1 Introduction

This article shows the research, development, exploitation and responsible disclosure of a zero-day vulnerability in the TP-Link Archer, Deco and Tapo series routers. Exploitation of this vulnerability can lead to a full compromise of the affected device. This vulnerability was found in an old firmware of the AXE75 router (2023), this vulnerability is however present in the most recent version of the firmware (November 4th 2024).

2 Terminology

This chapter gives some insight into the terms used throughout this article.

Term Description
Firmware A type of software that is directly placed onto a piece of hardware during manufacturing, it is like the “brains” of a device.
Emulating Emulating is the process of using one system to mimic or imitate another system so that it can run software or perform tasks as if it were that original system.
Reverse Engineering Reverse engineering is the process of taking something apart to understand how it works, often in order to replicate, modify, or improve it.
Vulnerability A vulnerability is a weakness or flaw in a system, software, or device that can be exploited by someone.
squash-fs SquashFS (Squash File System) is a type of compressed, read-only file system that is often used in Linux environments.
ubifs UBIFS (Unsorted Block Image File System) is a file system designed for flash memory devices (like NAND flash memory) in embedded systems.
kernel The kernel is the core part of an operating system (OS) that manages the hardware and software of a computer or device.
daemon A daemon is a type of program or process that runs in the background of a computer system, rather than being directly interacted with by the user
shell A shell is a program that allows users to interact with the operating system using text-based commands.

3 Obtaining the firmware

Getting the firmware for TP-Link routers is a straightforward process. TP-Link makes their firmware publicly available for anyone to download directly from their website. The image below illustrates how to download the firmware for a specific router model: Firmware download

4 Reversing the firmware

It is common for vendor firmware to be encrypted, requiring decryption before it can be examined for vulnerabilities. For example, D-Link encrypts their firmware, making analysis difficult. In contrast, TP-Link does not encrypt their firmware, although this could change in the future. While encryption helps protect against malicious actors attempting to reverse-engineer firmware, it also poses a challenge for security researchers working with good intentions.

4.1 Extracting files from the firmware

Firmware is a type of binary that contains other binaries, somewhat like a zip file. It includes a boot image, which holds the necessary files and instructions for starting up the system. This boot image is essential in the boot process because it contains the kernel. In addition to the boot image, the firmware also stores the files required for the device to operate correctly. On smaller devices, the kernel may access filesystems like squash-fs or ubifs to load and manage the system’s files.

The binwalk tool is primarily used to extract information from files and executable code, with its most common use being the extraction of files from firmware. To extract data from a firmware file, the following command is used: binwalk -e firmware.bin, where firmware.bin represents the firmware file. Binwalk will then extract the data and save it in a new folder named after the firmware file. The image below illustrates an example of the types of data that can be extracted from a firmware file: Binwalk output

The most notable folder here is the squashfs-root folder. Squashfs holds all the files found on the device when it’s running, meaning the squashfs-root folder represents the entire filesystem of the router, including all of its files. Filesystem of the router

This firmware is for a router, and routers typically provide a web interface for administrative tasks, such as configuring the WiFi password. Because of this, the www folder is a good place to begin exploring. This folder is also found on servers with a web server installed, though it is typically located in /var/ on those systems. www folder content

Upon opening the www folder, we find three subfolders and one file. The cgi-bin folder stands out here. The Common Gateway Interface (CGI) is an interface that tells the web server how to process data between the server and an application (source: stack-overflow.com). In this firmware, the cgi-bin folder contains just one file: luci

LuCI uses the Lua programming language and organizes the interface into logical components like models and views. This setup improves performance, keeps the installation size small, speeds up runtimes, and makes maintenance easier. Lua is ideal for devices with limited storage space (openwrt).

This firmware stores all the Lua files in the directory /usr/lib/lua/luci. There are 221 .lua files in total, which together provide the full functionality of the router. luci

5 Emulating the firmware

Before we can audit the firmware for vulnerabilities, we need a method to run it. This is typically achieved through one of the following approaches:

  • Using a physical device that hosts the firmware for testing;
  • Emulating the entire device using the kernel image and filesystem from the firmware;
  • Partially emulating specific components of the firmware for targeted vulnerability assessment.

I opted for the third option because I don’t have the physical device, and the second option was causing QEMU to crash. The third option also allowed to only emulate the binary that is responsible for serving the web gateway, which saved some computer resources.

Qemu-arm-static has been used to emulate only the web gateway of the TP-Link router. This form of emulation doesn’t require the kernel embedded in the firmware but instead utilizes the kernel of the host machine. Routers typically run lightweight HTTP servers; for example, D-Link uses lighttpd, while TP-Link uses uhttpd. Since this firmware is from TP-Link, we thus need to emulate uhttpd.

Firstly, we need to copy the qemu-arm-static binary over into the right target location. The command cp /usr/bin/qemu-arm-static /home/user/archer/_firmware.bin.extracted/squashfs-root/usr/bin copies the qemu-arm-static binary from our /usr/bin/ location into the /usr/bin location of the extracted firmware. Copying Qemu-arm-static

5.1 Emulating uhttpd

To emulate our target: uhttpd, we need to change our root directory. Linux comes pre-installed with the chroot binary. This binary allows a user to change the root directory of the process and of its children. Utilizing the command: sudo chroot . usr/bin/qemu-arm-static /usr/sbin/uhttpd -f -h /www -r Archer_AXE300 -x /cgi-bin -t 120 -T 30 -A 1 -n 3 -R -p 0.0.0.0:80 we achieve a couple things:

  • We change the root directory to the local directory (indicated by chroot .);
  • we start the qemu-arm-static that was copied to the /usr/bin folder;
  • We start the /usr/sbin/uhttpd binary that is embedded in the firmware;
  • We start uhttpd in the directory of /www;
  • We start uhttpd on IP and port: 0.0.0.0:80; Ubus failing

The image above indicates that something went wrong. The ubus failed to start. The ubus enables communication between processes running on the router, so lets fix this error message. To fix this error a couple commands need to be given, firstly these two commands:

  • sudo mount --bind /dev ./dev
  • sudo mount --bind /proc ./proc

These commands mount the /dev and /proc directories of the host machine to those of the emulated firmware.

Mounting the dev and proc folders

Now we need to make sure the ubus daemon is started up and ready to be used. To do this we can enter the device using the chroot binary. To get an interactive shell we need to start /bin/bash, which can be done like so: sudo chroot . bin/bash. Within this interactive shell we actually start the ubus daemon. This can be done with the command ubusd & which starts the ubus deamon in the background.

Starting Ubus

Now it displays a different error message, usock: not a directory. This error can be resolved by creating a /var/run directory in the squashfs-root folder. Like so: Creating /var/run folders

Now when re-issueing the ubusd & command it should start the ubusd without any problem. Once no error has occured we can start the uhttpd again.

Starting uhttpd

As you can see, there is no error and when we browse to localhost:80 we get the following screen:

TP-Link interface

We now have succesfully emulated the part of the TP-Link router that is responsible for the Web gateway.

6 Identifying vulnerabilities

Manually auditing every file on the filesystem is possible but highly inefficient and time-consuming. A more efficient approach to identify Remote Code Execution (RCE) vulnerabilities is to first locate any code capable of executing commands on the underlying operating system.

Once this code is identified, the next step is to trace its usage within the system. These functions are integral to how the firmware performs its tasks. For instance, CGI-BINs may require reverse engineering using a decompiler, while Lua scripts can often be audited simply by reading the code. TP-Link, for example, uses several functions to execute commands on the underlying OS, including:

  • os.execute
  • subprocess.call
  • sys.fork_exec
  • fork_call

After identifying these functions, we can begin investigating the mistakes left by the developers. There’s no need to manually go through all 200+ files; instead, we can use grep to search through the file contents and filter the results. For instance, to find instances of os.execute in all files, we can use the following command: grep -rni "os.execute". grep example

This command provides a list of all files that use os.execute, along with the line numbers where it’s called. The next step is to determine how and where this code is being invoked, to see if we can exploit it. For Remote Code Execution (RCE) to be possible, you must be able to manipulate the variables being passed to the system executor.

This method also lead to the discovery of this vulnerability: avira

The vulnerability that is highlighted here pertains to the avira.lua file. So what actually is Avira? funny enough Avira is an antivirus software that is tasked with protecting your device against attackers. Now this antivirus can be used to gain root (highest possible access) to the routers underlying operating system.

avira

Now I don’t blame the developers of avira, as a mistake is easily made especially in a file that is over 2000 lines long.

So why is this function vulnerable to a RCE? The answer lies in the ownerId variable. The function declares a local variable of OwnerId which is being filled by the data.ownerId variable. The data variable is being decoded from json from the app_from.data variable. The ownerId variable gets passed to the os.execute function on the last line while not being checked, sanitized or anything. The developers did note a --int comment to indicate that the ownerId is supposed to be an integer but it is just an assumption at this point.

vulnerable function

The function is named tmp_get_sites. We can use the same trick as with finding references to os.execute. We just change the grep command a bit to the following command: grep -rni "tmp_get_sites". cross-reference function

We can see that the tmp_get_sites is being used in the file: lib/lua/luci/controller/admin/smart_network. This file is only accessable when a user or attacker is logged in, as indicated by the admin folder.

When opening the smart_networks.lua file we see the following all the way at the bottom. This is how TP-Link made their functions callable:

smart_network endpoint

We need to make the request to /admin/smart_network?form=tmp_avira and the operation needs to be getInsightSites to trigger the vulnerable function. It is an operation because this is how TP-Link has defined it in their code.

Now this operation calls to the function tmp_get_sites this however is not the tmp_get_sites as defined in the avira.lua. This function is re-defined within the smart_network endpoint but it does point to the tmp_get_sites from avira.

get_insight

As we can also see in this function, there is no checking nor sanitization of any kind. The app_from that is parsed here gets directly parsed to the AVIRA:tmp_get_sites function.

7 Exploitation

We now know it is vulnerable, the next step is actually proving it is vulnerable. The way to do this is to gather all the knowledge we have thus far have obtained. We know we need to call an app_from, we know we need to set certain parameters to exploit and we know we need to authenticate.

I want to give major props to aaronsvk at https://github.com/aaronsvk for already making the authentication part of this exploit code. I did not have to re-invent the wheel and I majorly appreciate that.

The vulnerability can be exploited by placing a payload in the ownerid value. The date is set to the current day, serving no purpose other than preventing a null value for the parameter. The type is set to “visit,” which is crucial for exploiting this vulnerability. Additionally, both the startIndex and amount are configured to ensure they do not null the parameters in the code. I have tested the exploit without these parameters set, but this would lead to a failed exploit attempt.

exploit code

The full exploit code can be found on: https://github.com/ThottySploity/CVE-2024-53375

Now what can we actually achieve with this exploit?

Well, we run as root run as root

We can dump any file that we like.. want to see /etc/passwd and /etc/shadow: passwdshadow

8 Mitigation

The mitigations for this vulnerabilities would be to sanitize the input of ownerId. This can be done using the function tonumber in Lua. This would mitigate any injected text.

mitigation

9 Responsible Disclosure

When finding a zero-day the most responsible action to do is disclose it with the vendor.

  • 03.10.2024 - Identified vulnerability
  • 04.10.2024 - Contacted Zero Day Initiative (ZDI)
  • 10.10.2024 - ZDI was not interested in aquiring the vulnerability
  • 10.10.2024 - Contacted TP-Link with information about the vulnerability
  • 12.10.2024 - TP-Link forwarded information to person for analysis
  • 30.10.2024 - Requested CVE-ID from MITRE
  • 08.11.2024 - TP-Link acknowledged vulnerability and provided fixed beta firmware version
  • 23.11.2024 - MITRE reserved CVE-ID 2024-53375

10 References