Claroty Team82 participated last fall in the Pwn2Own 2023 Toronto IoT hacking contest and exploited TP-Link ER605 routers and Synology BC500 IP cameras
We demonstrate via this research how an attacker can compromise a device connected to the wide-area network and move to the local network in order to compromise a connected IoT device.
In part one of this series, we explained our research and attack on TP-Link ER605 routers. Here we cover how we moved from the router to the local network to compromise the Synology BC500 IP camera.
TP-Link fixed the vulnerabilities we reported in firmware version ER605 (UN) V22. 2.4 Build 20240119
Synology fixed the vulnerabilities we reported in firmware version 1.0.7-0298 and released a security advisory.
In part one of this two-part series, we explained the first half of Team82’s WAN-to-LAN pivot attack whereby we gained access to TP Link ER605 routers, using a chain of exploits to bypass NAT protection and gain remote code execution on the router.
After gaining full access to the router using our WAN exploit, we needed to pivot inside the LAN to exploit the IoT device we selected: a Synology BC500 IP Camera.
The Synology BC500 IP camera is a 5-megapixel wide-angle camera with an IP67 rating, suitable for indoor and outdoor applications. They offer valuable features such as night vision and motion detection, making them particularly beneficial for businesses looking to remotely monitor and capture suspicious activities.
The Synology BC500 IP Camera is a 32-bit ARMv7 architecture based device with a customized Linux operating system. The firmware is not encrypted and can be downloaded from Synology's website. The firmware is packed and signed by the vendor. It is partitioned into several segments which hold different parts of the device’s system including the Linux kernel and a UBI rootfs
filesystem.
The camera has a web interface which is based on the civetweb web server serving C/C++ based CGI executables. The web server’s configuration is located at /etc/webd.conf
and it binds to ports 80 (HTTP) and 443 (HTTPS).
During our research, we discovered a vulnerability in the JSON parsing routine used by a CGI binary, /www/camera-cgi/synocm_param.cgi
which is accessible by the following HTTP URL, /syno-api/activate
. When examining this binary, we noticed that upon receiving a request with method type PUT
, it tries to parse the JSON contents from the request body using the open source library libjansson.
To better explore this code flow we created a setup where we can debug the binary using the firmware.
When processing the PUT
request the binary calls the json_loads() function. This method deserializes the JSON string received from the user into a JSON object that the application can work with.
While looking at the implementation of the json_loads
function, we noticed a call to a dangerous sscanf
function. This C function is considered unsafe, since it could potentially cause memory corruption issues.
As you can see in the image below, the insecure call to sscanf
splits the key element, which can be supplied by an unauthenticated remote client, into two strings stored locally on the stack.
Since the binary does not limit the user-input size parsed by the json_loads
function, a stack corruption vulnerability exists in this routine, enabling attackers to overflow the stack structure. It’s important to note that the call to the dangerous method sscanf exists only in the libjansson
library on Synology’s camera, and not in the original open source library.
The two output strings located on the stack are small (32 bytes and 12 bytes buffers) and can be overrun very easily by a malicious attacker with network access to the camera.
With this insecure call, a stack buffer overflow can occur easily by supplying a JSON object with the key element containing two long sequences of characters separated by a space, like so:
{“32+_bytes_string 12+_bytes_string”: “”}
This issue is even more severe as this API route does not require valid authentication credentials, meaning even unauthenticated attackers can exploit this vulnerability remotely.
Using checksec, we analyzed the mitigations found in the main program synocm_param.cgi
and in the JSON library /lib/libjansson.so.4.7.0
. We found that while the main application process has almost all modern binary mitigations enabled including RELRO, NX
, etc, the library was not compiled with stack canary protection.
The camera’s OS enforces ASLR as a security measure. This meant we could not rely on static addresses in our exploit payloads. After analyzing the execution of the binary several times, and analyzing the process mapping from /proc/[PID]/maps
, we came to the conclusion that the application has 8 bits of address randomness, and that the heap has 12 bits of randomness in its address location.
The ASLR protection and the context in which we run meant we really had only one option. We needed to brute-force the addresses used by the program when attempting to exploit the device. After examining the configuration of the web server at /etc/webd.conf
, we found that it enables 10 concurrent requests at the same time, which means we could have up to potentially 10 parallel requests at any given time.
In order to execute arbitrary code on the camera, we decided that the best approach for our exploit would be calling the system standard library function.
The first part of our exploit chain was finding a way to redirect code execution. After some debugging, we managed to find a function pointer on the stack located after our overrun buffer variables that we can fully overwrite and force the program to use. This function pointer points to the function get_func
, a function which iterates over the user’s JSON-string. This pointer is a member of an object called stream_t which is contained in another object called lex_t
.
To control code flow, we start from overflowing the stack frame inside the vulnerable function parse_object. We then overflow the stack until we overwrite the function pointer (get_func
) with the address of the system function.
As we can see, immediately after the sscanf
call, the program calls the lex_scan
function. Later on in that code flow, the program will use the stack function pointer we overrun and invoke it, calling the function it points to. Since we overwrite this pointer, we now have the ability to control the code flow and jump to whatever routine we choose.
Entering parse_object
to parse JSON-string →
overflow stream_t struct
on the stack using sscanf
→
lex_scan
→
steam_get
→
stream.get()
invokes our controlled pointer
Our payload is composed of different parts, each with different purposes. Two of these parts are the system
function address (which is relative to the program base address: <Program-Base> + 0x3f190), and a pointer to the program’s heap region containing our OS command that will be invoked by the system function.
In order to make sure our heap pointer actually points at our OS command, we sprayed the heap with big chunks of data, composed of padding and the actual OS command at the end. This increased the success rate of our exploit, since it meant the heap was filled up with our sprayed payload. Which consisted of our padding that acted as a slider leading to our OS command. An important remark is that the limitation of OS commands inside the system function is OS and kernel configuration dependent and in our case the maximum system command was 0x20000
, meaning our overall payload including the “slider” padding could be at most 0x20000
bytes long.
To deduce where we should point our address to the command buffer, we sampled several execution attempts and calculated the optimal address that overlaps with most execution heaps.
Another important aspect we took into account was the ability to insert null bytes into our payload. Our exploit required null-bytes because we were using pointers that pointed to the application memory address range, which was in the 0x00400000 range. Therefore, we needed to find a reliable way to add null-bytes into our overflow in the stack.
We had one null byte inserted at the end of our payload by sscanf
and another one by inserting a space character due to the string format supplied to the sscanf
function (%s %s
). By overriding the second split key and only then the first part of the key we were able to reliably “inject” two null-bytes into our payload.
For example, imagine we are giving this JSON string key:
{“YYYYYYYYYYYYYYYYYYYYYYYY XXXXXXXXX”: “”}
After sscanf
with “%s %s
” This will be translated to two split parts:
YYYYYYYYYYYYYYYYYYYYYYYY00
XXXXXXXXX00
And finally the overwritten stack will contain two null-bytes carefully placed where we needed them:
XXXXXXXXX00YYYYYYYYYYYYYYYYYYYYYYYY00
In addition, we had another problem: How can we reliably encode non-ASCII bytes into our exploit payload? The problem is that JSON keys can only be valid UTF-8 encoded strings. To overcome this, we created a huge table with all the UTF-8 encoded potential bytes and then carefully chose the initial input that led to the creation of these bytes.
For example, to encode the address 0xA68D9FF0
we needed to encode the following bytes \xf0\x9f\x8d\xa6
and therefore, we would use the unicode symbol 🍦 which represents an ice cream EMOJI.
And finally we could encode our payload with specific pointers based on our exploit needs.
Our payload consists of a JSON object with two items, the first is a key:array[]
of 6 strings composed of padding and OS command, and the second triggers the function pointer overflow explained above.
Indeed after executing our exploit against the target, we got a reverse shell and full control over the IP Camera.
Synology has fixed this vulnerability in firmware version 1.0.7-0298 and issued an advisory.
Overall it was a fun challenge to find a practical way to exploit a SOHO router from the WAN and pivot into the LAN by exploiting one of the connected IoT devices. We chose TP-Link ER605 as the router and Synology BC500 IP Camera as for the IoT device and used four vulnerabilities chained together to exploit these devices one after the other. Unfortunately for us it was a partial win because the exact same Synology Camera exploit was used before our turn in the competition. Therefore, we finished the competition with $40,750 and 8.25 Master of Pwn points.
Acknowledgement
As always, we would like to thank ZDI for organizing this Pwn2Own (our fifth competition!). These hacking events are amazing and raise the security bar for all vendors. We truly believe that now all the products that were targeted in the event are MUCH more secure.
In addition, we would like to thank both Synology and TP-Link for fixing all the vulnerabilities we reported.
CWE-120 BUFFER COPY WITHOUT CHECKING SIZE OF INPUT ('CLASSIC BUFFER OVERFLOW'):
A denial-of-service vulnerability exists in the affected product. The vulnerability results in a buffer overflow, potentially causing denial-of-service condition.
Rockwell Automation has corrected these problems in firmware revision 4.020 and recommends users upgrade to the latest version available.
CVSS v3: 9.8
CWE-122 HEAP-BASED BUFFER OVERFLOW:
A denial-of-service and possible remote code execution vulnerability exists in the affected product. The vulnerability results in the corruption of the heap memory, which may compromise the integrity of the system, potentially allowing for remote code execution or a denial-of-service attack.
Rockwell Automation has corrected these problems in firmware revision 4.020 and recommends users upgrade to the latest version available.
CVSS v3: 9.8
CWE-420 UNPROTECTED ALTERNATE CHANNEL:
A device takeover vulnerability exists in the affected product. This vulnerability allows configuration of a new Policyholder user without any authentication via API. Policyholder user is the most privileged user that can perform edit operations, creating admin users and performing factory reset.
Rockwell Automation has corrected these problems in firmware revision 4.020 and recommends users upgrade to the latest version available.
CVSS v3: 9.8
CWE-191 INTEGER UNDERFLOW (WRAP OR WRAPAROUND):
The affected product is vulnerable to an integer underflow. An unauthenticated attacker could send a malformed HTTP Requesty, which could allow the attacker to crash the program.
Planet Technology recommends users upgrade to version 1.305b241111 or later.
CVSS v3: 5.3
CWE-78 IMPROPER NEUTRALIZATION OF SPECIAL ELEMENTS USED IN AN OS COMMAND ('OS COMMAND INJECTION'):
The affected product is vulnerable to a command injection. An unauthenticated attacker could send commands through a malicious HTTP request which could result in remote code execution.
Planet Technology recommends users upgrade to version 1.305b241111 or later.
CVSS v3: 9.8