Team82 Logo Claroty
Return to Team82 Research

Pwn2Own: Pivoting from WAN to LAN to Attack a Synology BC500 IP Camera, Part 2

/

Executive Summary

  • 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.

Synology BC500 IP Camera

Technical Details

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.

Stack Buffer Overflow Vulnerability (ZDI-24-833)

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.

JSON string example from gdb debugging session of the program

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.

parse_object implementation contains insecure sscanf with user input

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. 

Buffer variables on the stack that get overrun by a malicious payload

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. 

Exploit Binary Mitigations

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.

checksec output for the cgi and library - library doesn’t have Stack-Canaries on the stack

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.

Sampling of different synocam_param.cgi text segment base addresses
Sampling of different synocam_param.cgi heap segment base addresses

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.

/etc/webd.conf configuration file with number of threads equal to 10

Exploitation Path

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.

From the open-source libjansson library: lex_t and stream_t structs and the get_func pointer which we override.

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.

Function decompilation view of the parse_object(), calling sscanf with user input and then calling the TG_lex_scan 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

lex_scan() implementation - Calling to lex_get() in order to get the next character from the JSON-string

Our function pointer being invoked in stream_get().

The Exploit Payload

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.

System Slider Padding

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.

Carefully Selecting Heap Pointers

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.

A diagram showing heap region distribution across the memory of the program (each color represents a different session on the device after reboot)

Injecting Null-Bytes to Our Payload

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

Encoding Our Exploit

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.

We created a huge table with all UTF-8 possible encoded data.

And finally we could encode our payload with specific pointers based on our exploit needs.

Payload system() function address encoded with UTF-8

LAN Exploit Summary

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.

Explanation OF payload structure

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.

WAN-to-LAN Exploit Summary

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.

Stay in the know Get the Team82 Newsletter
Recent Vulnerability Disclosures
Claroty
LinkedIn Twitter YouTube Facebook