
Une implémentation d'une preuve de concept pour CVE-2020-12124
originellement sur https://www.klogixsecurity.com/scorpion-labs-blog/anatomy-of-an-iot-exploit-from-hands-on-to-rce
par David E. Baker, publié le 1er juin 2023
Cette étude concerne le firmware du routeur Gigabit Wavlink Wireless-AC1200 tel qu'il était en juin 2020. Les vulnérabilités discutées ici peuvent ou non avoir été corrigées par le vendeur, mais il s'agit d'un cas de recherche de vulnérabilités qui montrera que le voyage est la récompense plutôt que sa conclusion. L'auteur a mené cette recherche avant la divulgation publique des vulnérabilités, mais après qu'elles aient été découvertes indépendamment par d'autres chercheurs et signalées au vendeur.
Le vendeur rend le firmware de ses produits disponible dans la section support de son site web ; c'est un moyen courant d'obtenir le firmware IoT et une alternative utile à son extraction depuis la mémoire de l'appareil. Le firmware n'est pas chiffré, il peut donc être facilement extrait avec binwalk. L'analyse dynamique a été effectuée avec l'accès à un exemplaire physique de l'appareil, et l'analyse statique via Ghidra.
L'interface web du routeur Gigabit Wavlink Wireless-AC1200 présente plusieurs points d'accès vulnérables qui permettent la copie sans restriction des données fournies par l'utilisateur sur la pile applicative, voire directement dans la ligne de commande, pour parvenir à une exécution de commande arbitraire.
Les scans initiaux de l'appareil suggèrent que la seule ressource exposée était la console d'administration web, accessible aux utilisateurs authentifiés sur l'interface LAN via HTTP sur le port TCP 80. L'appareil peut offrir davantage de services, mais ceux-ci ne sont pas activés par défaut. Par conséquent, cette enquête se concentre uniquement sur l'interface web.
Un scan nmap de l'appareil exemple, montrant uniquement l'interface web à l'écoute.
Les tests habituels — comme les injections de commande typiques sur les panneaux de diagnostic de l'appareil qui permettent une injection de commande dans les paramètres d'une commande ping ou traceroute — n'ont pas donné de résultats immédiatement intéressants, ce qui était décevant.
Options de gestion disponibles après authentification sur le panneau d'administration web. "Stockage USB" apparaît comme la deuxième option.
La première interface (finalement exploitable) examinée ici se trouvait sur le panneau "Stockage USB", visible comme la deuxième option dans la capture d'écran ci-dessus. L'appareil dispose d'un port USB adjacent à ses prises Ethernet 802.2, suggérant qu'il pourrait offrir une fonctionnalité de stockage en réseau (NAS).
Une photographie de l'arrière de l'exemplaire réel, montrant la disponibilité USB.
Un principe simple en recherche de vulnérabilités est que plus un morceau de code interagit avec de composants et plus il comporte de pièces mobiles, plus il est probable que du code exploitable se cache à proximité. La présence de capacités NAS est prometteuse car elle indique la présence de code qui interagit simultanément avec la couche logicielle de l'appareil, la couche matérielle et la périphérie branchée (le stockage USB lui-même).
L'interface de gestion de la console de stockage USB est présentée ci-dessous. La simple présence du champ "Groupe de travail" est prometteuse, car cela suggère que ce routeur WiFi pourrait même tenter d'interagir via le protocole Server Message Block (SMB) — une lourde tâche pour un routeur IoT. Je ne compte plus le nombre de fois où j'ai vu une entrée fournie par l'utilisateur envoyée directement à la ligne de commande comme argument de la fonction Unix smbpasswd.
Options de stockage USB disponibles pour les utilisateurs authentifiés.
Les tentatives initiales de modification de ces paramètres ont échoué car l'appareil ne détectait pas de disque USB, comme on le voit ci-dessous.
Les modifications de configuration des options de stockage USB ne seront pas enregistrées à moins qu'un disque correctement formaté ne soit branché manuellement sur le port USB de l'appareil.
Cependant, une fois un disque correctement formaté branché, l'appareil a permis de définir un nom d'utilisateur et un mot de passe FTP. Comme suspecté, il a placé cette entrée utilisateur sur la ligne de commande :
Une injection de commande dans le champ 'mot de passe' offre le premier accès shell directement au système d'exploitation de l'appareil.
Bien qu'intéressante, cette vulnérabilité est difficile à considérer comme très excitante : elle nécessite non seulement un accès authentifié à l'interface d'administration de l'appareil, mais aussi un accès physique à l'appareil pour manipuler son disque USB. L'exploit ci-dessus permet à un chercheur d'interagir avec les composants individuels du système d'exploitation (et de les exfiltrer à des fins de rétro-ingénierie).
L'appareil était un système Linux basé sur busybox avec une interface web alimentée par Lighttpd. La fonctionnalité CGI (Common Gateway Interface) était assurée par des binaires individuels dans /etc_ro/lighttpd/www/cgi-bin/, les requêtes web vers les URI CGI lançant ces binaires directement. Un rapide coup d'œil à nas.cgi dans Ghidra montre l'injection de commande à la ligne 38 ci-dessous, qui envoie un mot de passe fourni par l'utilisateur directement à la fonction do_system (elle-même simplement une enveloppe autour de l'appel système standard de la libc).
L'entrée utilisateur est placée sur la ligne de commande comme argument du script chpasswd.sh à la ligne 38, résultant en une injection de commande et un accès shell directement au système d'exploitation de l'appareil.
En parcourant le répertoire /cgi-bin/, la tâche de trouver un exploit plus intéressant se réduit à énumérer les interfaces CGI disponibles pour l'utilisateur, comme montré ci-dessous :
Une liste exhaustive des binaires CGI mis à disposition sur l'appareil, prise en direct du shell établi par l'exploit décrit dans cette section.
Plusieurs choses ressortent lors de l'examen initial. La première chose importante à noter est que les binaires CGI appellent souvent la fonction check_valid_user. Cette méthode vérifie si l'adresse IP qui fait la requête est stockée dans un fichier temporaire particulier du système de fichiers. Des tests minimaux montrent que le statut d'authentification d'un client n'est vérifié qu'après l'appel de cette méthode, donc l'ensemble de la surface de code dans chaque binaire CGI avant l'invocation de cette fonction est accessible sans authentification.
La décompilation de adm.cgi montrant la méthode check\_valid\_user. Tout le code précédant cet appel est exécuté avant de vérifier le statut d'authentification du demandeur.
Une autre observation intéressante est le grand nombre de méthodes qui copient l'entrée fournie par l'utilisateur directement sur la pile. Par exemple, la décompilation de wireless.cgi montre le paramètre NewName tiré du corps d'une requête web aux lignes 14 et 15, suivi d'un strcpy non protégé de cette entrée utilisateur sur la pile à la ligne 34, comme montré ci-dessous :
Les lignes 14 et 34 démontrent un strcpy non protégé du paramètre NewName du corps de la requête directement sur la pile du programme.
Cette copie de l'entrée utilisateur sur la pile suggère à elle seule une exploitation par corruption mémoire, ce que la commande suivante peut vérifier :
curl -XPOST --data "page=SetName&NewName=\`python3 -c 'print(\\"A\\"*(512))'\`" http://target-ip/cgi-bin/wireless.cgi
Bien que prometteuse, une attaque orientée retour n'est pas optimale ici. En utilisant l'accès shell au système d'exploitation déjà obtenu, la commande suivante affiche '1', montrant que l'ASLR (Address Space Layout Randomization) est faiblement appliquée, laissant une chance sur 256 qu'une chaîne ROP atterrisse sur le gadget souhaité :
# cat /proc/sys/kernel/randomize\_va\_space
1
De plus, la commande ci-dessous montre que les binaires little-endian sont compilés avec l'octet de garde de pile activé, donc seul le dernier gadget d'une chaîne peut atterrir à un emplacement prédéterminé dans le binaire. Si un processus de surveillance est présent pour redémarrer le serveur web après un crash, il n'est pas exclu de lancer répétitivement un exploit orienté retour et d'espérer un succès éventuel, mais il est également possible que de meilleurs bugs se cachent ailleurs dans le code.
xxd /etc\_ro/lighttpd/www/cgi-bin/wireless.cgi | head -n 10
En continuant à examiner les fonctions CGI, on finit par examiner live\_api.cgi. Ce binaire n'appelle pas check\_valid\_user, donc toute requête web vers l'URI /cgi-bin/live\_api.cgi exécute cette application CGI sans authentification. La ligne 9 dans la Figure 11 montre que la variable d'environnement QUERY\_STRING, qui (selon la spécification CGI d'Apache) est la partie de l'URI de la requête immédiatement après un point d'interrogation et donc fournie par l'utilisateur, est stockée dans pcVar1 et à la ligne 19 de la Figure 11 est envoyée à la méthode satellite_status.
Une décompilation de live\_api.cgi montrant l'entrée utilisateur prise de l'URI à la ligne 9 et envoyée à satellite\_status à la ligne 19.
La décompilation de la méthode satellite_status, montrée dans la Figure 12, indique que la chaîne de requête elle-même (maintenant param\_1) est analysée pour extraire les paramètres page, id et ip. Le paramètre ip est copié via la fonction sprintf dans une variable locale à la ligne 38 de la Figure 12 et, à la ligne 39, passé à la fonction do\_system. L'absence d'appel à check\_user\_auth indique que l'entrée arbitraire d'un client non authentifié dans l'URI sera placée directement sur la ligne de commande via le paramètre URI ip, confirmé ci-dessous :
Une décompilation de la fonction satellite\_status, qui montre la chaîne de requête (maintenant param\_1) analysée pour le paramètre ip aux lignes 22 et 23, puis un appel à do\_system aux lignes 38 et 39.
La preuve est dans le pudding, montrant l'exploit utilisé pour effectuer une prise de contrôle à distance de l'appareil.
Trouver des exploits sur un appareil IoT fraîchement arrivé sur le marché peut sembler une cible facile, comme mentionné au début de cet article, mais la valeur de cette enquête résidait dans son parcours plutôt que dans sa destination.
Une compréhension du contournement de l'authentification et de l'emplacement de l'injection de commande aurait été peu probable, voire impossible, sans une analyse statique du firmware. S'il n'avait pas été disponible en ligne, cela aurait nécessité un accès dynamique au système d'exploitation de l'appareil pour les obtenir, pour lequel les chances d'accès dynamique à l'appareil étaient faibles. Ce niveau d'accès lui-même reposait à la fois sur un accès physique à l'appareil et sur du matériel spécialisé ou une supposition éclairée quant aux endroits probables du code où des erreurs auraient pu être commises.
Nous espérons que ce voyage vous a plu et que vous reviendrez pour en savoir plus. Bon hacking !
Auteur :
David Baker, Consultant Senior en Sécurité, Tests, K logix