Team82 Logo Claroty
Return to Team82 Research

Pwn2Own: WAN-to-LAN Exploit Showcase, Part 1

/

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 explain our research and attack on TP-Link ER605 routers. Part two will 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.

Pwn2Own Toronto 2023: IoT Edition | SOHO Mashup Exploit

Introduction

Almost anyone connecting to the internet from a small office / home office (SOHO) network does so from a similar network layout. The most essential device to bridge between the local network to the internet is the router. Routers not only help with routing network traffic but also act as a safeguard dividing the internal local area network (LAN) and the external internet, the wide-area network (WAN), preventing unwanted external access to internal devices. Therefore, exploiting routers from the WAN and bypassing network address translation (NAT) is dangerous and poses a significant risk to the local network.

At the 2023 Pwn2Own Toronto competition, we demonstrated our WAN-to-LAN pivot attack, exploiting a TP-Link router from the WAN. In our research we looked into ways an attacker can infiltrate from WAN to LAN, uncovering vulnerabilities in TP-Link routers allowing attackers to bypass NAT protection. After gaining remote code execution (RCE) on the router, we pivoted to the LAN and developed an exploit against a Synology IP camera by moving laterally inside the network.

In this blog, we are going to present our research, including advanced embedded exploitation techniques, and our approach and methodologies into discovering a new NAT bypass and IoT device vulnerabilities.

In part one of this two-part series, we’ll cover our techniques for exploiting routers; in part two, we’ll explain how we used the routers to move from the WAN to the LAN. 

So buckle up, we have a long ride!

WAN Exploitation: TP-Link ER605 Router

The TP-Link ER605 router is featured as a VPN router for consumers and small businesses.

The device is described to have security measures such as firewall policies and denial-of-service (DoS) defenses. This router also has cloud-based management interfaces.

Technical Details

Since we wanted to achieve WAN exploitation, we searched for WAN-related attack surfaces. One feature the router supports is the dynamic DNS (DDNS) service. DDNS enables a device with dynamic IP to hold a domain-name record that dynamically changes when its IP changes. One of the DDNS providers TP-Link chose to support is Comexe, which uses a custom DDNS protocol to communicate with the provider’s server. To interact with the provider of the service the device uses a binary located at /usr/sbin/cmxddnsd. While researching the cmxddnsd binary we found three vulnerabilities that when chained together allowed us to achieve remote code execution from the WAN side.

Vuln #1: Improper Validation of Server Authenticity (CVE-2024-5242)

We started by looking at the binary cmxddnsd. During boot and periodically afterwards, the binary tries to access its DDNS servers in order to start a DDNS connection and update its external IP address. By default, the router is configured with two Comexe DDNS servers Dns1.comexe.net, Dns1.comexe.cn.

In order to get the server’s IP address, cmxddnsd tries to resolve the DDNS server's DNS name by querying the router’s DNS resolver. After retrieving the IP address, the binary initiates a UDP connection to the DDNS server over UDP/9994. The protocol used to communicate with the DDNS servers is proprietary, so we had to research the binary to better understand the protocol. 

After some research, we discovered that the protocol used for the DDNS communication is as follows: each message is composed of two segments:

  1. an encrypted Data segment

  2. a message segment called ‘C’ (C=1 for instance) indicating the message direction (server to client or client to server). In order to separate the different message parts, Comexe used a single byte of \x01 to separate the different parts. 

A DDNS request and response, containing the encrypted Data segment of the request.

In order to encrypt/decrypt the Data segment of the message, Comexe uses the 3DES scheme for symmetric encryption, using a hardcoded 8-byte key:  \x53\x76********* [CENSORED]. After the encryption routine, the data is encoded using Base64 with a custom base64 character mapping. After the decryption process is complete the decrypted content is stored in the DATA segment. The decrypted content is structured with several parts of the message, such as the message type (authentication request, authentication response, etc.), ASCII-based parameters (such as username/password pair, IP address etc), and the error code of the request.


By researching the cmxddnsd binary, we discovered that no actual host validation occurs as part of the Comexe DDNS protocol. The only thing needed to impersonate a client/server in this DDNS protocol is knowing the encryption procedure and the hardcoded key used by the different parties. This meant we could impersonate the provider server to receive and send requests to the client device.

Encryption/Decryption constants used in our scripts.

In order for a client to validate a server’s identity during connection, most protocols use SSL or an asymmetric encryption algorithm to validate each side’s true identity. However, since Comexe uses a symmetric encryption algorithm, it is possible for anyone with knowledge of the encryption key (which is embedded inside the device) to impersonate each side.

Vuln #2: Remote Code Execution due to stack based overflow (CVE-2024-5243)

After gaining the ability to impersonate the DDNSprovider, we started investigating the parsing flow and DDNS functionality in cmxddnsd. Soon we identified the function handling DDNS packet processing inside the binary: _chkPkt.

Part of the _chkPkt function, handling Data decryption.

As part of the communication protocol, the server sends an ASCII-based payload composed of several segments, each segment relevant to specific message type and parameter. For example the DDNS server can send an errorCode parameter which indicates the specific error that occurred, or an updateSvr parameter, which tells the client to update its version of the provider’s server domain for example from Dns1.comexe.cn to mynewdomain.com.

We discovered that during parsing routines of specific message parts, the server copies the received parameter buffers without bound checking nor does it validate for safety. For example, involving the errorCode parameter, the server uses strncpy to copy the buffer into a stack variable of 4 bytes. However, the copy uses the length of the received parameter and not the destination buffer, which might be smaller. This means that if the router receives an errorCode data of size bigger than 4 characters, a buffer overrun will occur.

The code causing the overflow bug we exploit, inside the _chkPkt function.

Since the attacker fully controls the payload, they could send content with large sizes inside the errorCode parameter, only being limited by the maximum packet size 0x800. This will trigger a stack based buffer overflow and as a result could affect the code execution flow of the application.

The stack state before our overflow in the errorCode parsing flow at _chkPkt. We can see that a valid application address is saved before the current stack pointer and inside the $ra register.
The stack state after our overflow in the errorCode parsing flow at _chkPkt. We can see we managed to overflow the return address register with a value we control (0x77fa62f0), meaning we overtook the execution flow.

Similar buffer overflow vulnerabilities existed in different commands inside the DDNS protocol. For example, inside the handling of the UpdateSvr1 and UpdateSvr2 parameters, a user-controlled buffer is copied into a small buffer in the globals section with constant length of 0x80 bytes long, overflowing the global section of the program with user-controlled new Comexe DNS server domains.

Abusing the UpdateSvr1, an attacker can send a payload bigger than 0x80 in order to overflow the global memory section and overwrite other structs.

After verifying different stack-based buffer overflows, we chose to focus on overflowing the errorCode parameter (cmxddnsd ! 0x2d8a) while overwriting the entire stack in the _checkPkt frame. Since there are no stack canaries protecting it, we now had the ability to control the execution flow and jump to our controlled $pc (program counter).

Our plan for successful exploitation and remote code execution was to create a ROP chain (return-oriented programming) that leads to a system (COMMAND). The problem we faced is address space layout randomization (ASLR); we could not guess the base addresses of the stack/heap/libs and needed to find a way to leak them in order to defeat ASLR, a mitigation in many operating systems against memory-based code-execution attacks.

Vuln #3: ASLR bypass due to OOB read (CVE-2024-5244)

After managing to control the execution flow of the program, we could supply addresses for the program to jump to, and continue, execution from them. However, in a modern OS, ASLR mitigates attacks such as this by randomizing the program address layout, loading different sections like libraries, heap and stack to random locations on each execution. In order to successfully exploit the above vulnerability and execute arbitrary code, we had to bypass ASLR in order to have valid addresses for our ROP chain.

To bypass the ASLR protection, we had to leak valid addresses from the program memory, pointing to the randomized memory sections. This is so we could calculate the base address of the libraries from these leaked pointers. To achieve this goal, we looked for a way to exfiltrate out of bounds memory that contains pointers to valid addresses. 

We started by examining the code sections in the program sending data to remote servers. Soon enough we discovered that the cmxddnsd binary only communicates in two protocols:  DDNS (Comexe proprietary UDP/9994) and DNS (UDP/53). To find memory leaks in these protocols, we started looking at the functions and procedures handling these requests.

When inspecting the function sndDnsQuery, we noticed that it was vulnerable to a stack-based buffer overflow. The overflow in this function occurs when the program reads a DNS name from a global section of memory, and copies it to its stack one character at a time.

While usually the DNS name is limited to a size of 0x80, since we managed to overflow the global section variable using UpdateSvr1 and UpdateSvr2 update, we were able to supply a much bigger DNS name.

During its execution, the function will copy one byte at a time from our overflowed global variable into the stack, which could lead to a stack-based buffer overflow. The only interruption to copying each byte comes when the function encounters a forward slash (/) character or a null-byte (\x00). Since we control the DNS name and its size, we can overflow anything on the stack in the sndDnsQuery frame.

_sndDnsQuery() function, vulnerable to stack-based overflow by copying the DNS server names onto a stack variable

We exploited this stack-based buffer overflow to overflow and rewrite one specific stack variable, sendSize. This variable is then used by the program to dictate how many bytes the program will send in the DNS query. By modifying the lower two bytes of this variable, we were able to overwrite a very large number, much bigger than the actual domain and the DNS payload.

Therefore, whenever the program tries to send the DNS query, since the sendSize integer is bigger, it will keep on sending data from the stack; sending as many bytes as the number inside sendSize. That approach leads to out-of-bounds memory read and leaking pointers from the stack using DNS.

Stack layout of the function _sndDnsQuery. We start our overflow in the domain_name variable, and overflow the lower two bytes of send_size.

Another view on the stack layout of the function _sndDnsQuery including our specific overflow to control the leak size

Using the out-of-bounds read, we managed to leak many pointers sitting on the stack. These pointers point to many different memory regions, including the stack itself, the heap and even to different libraries such as libc. Using these pointers, we managed to calculate the current base-addresses of these memory regions, allowing us to bypass the ASLR feature.

The Full WAN Exploit Chain

To fully exploit the vulnerability chain shown above, an attacker first needs to main-in-the-middle (MiTM) traffic between the router and the Comexe DDNS servers (using the vulnerability from Issue #1).

Then, the attacker will supply a malicious UpdateSvr1 value to the server, bigger than the maximum size for this variable This will then cause a buffer overflow during the sndDnsQuery procedure, overflowing the sendSize variable, triggering our Out-of-Bounds read vulnerability (Issue #3).

Lastly, using the base addresses the attacker managed to leak using Issue #3, the attacker will send a malicious DDNS response with a specially crafted errorCode parameter, triggering a stack-based buffer overflow as shown in Issue #2. This will cause the program to execute the ROP-chain crafted by the attacker, handing over execution flow to the attacker and triggering an OS command under the attacker’s control. This means the attacker now has full control over the attacked router, allowing them to execute arbitrary OS commands in the permissions of the root user.

WAN Exploit Summary

  1. Sit behind the router’s WAN - MITM

  2. Initiate DDNS domain updates using UpdateSvr1, UpdateSvr2 with overflowed domains

  3. Overflow the stack in sndDnsQuery without crashing the application and leak pointers from the stack

  4. Calculate base addresses for the stack and libc

  5. Send DDNS request with malicious errorCode to overflow the stack in the _checkPkt frame

  6. Overflow the stack and start ROP chain

  7. Call System with our payload

Preparing to Pivot to LAN Attack

Now we had full remote root access to the router. We needed to establish dominance so we completely opened its firewall rules (clearing iptables) and configured a reverse shell that will try to reach us in case the connection is closed.


Next, we needed to find what devices are located behind the router on the LAN, so we both sniffed and did arp -an and discovered the IoT device we were looking for, a Synology BC500 IP Camera. Once detected, we created a “proxy” using SOCAT (listen-connect) on the router that will expose the web port of the camera to us and continue to phase 2, pivoting into the LAN.

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