
Divulgation des vulnérabilités des caméras Accfly : CVE-2020-25782, CVE-2020-25783, CVE-2020-25784, CVE-2020-25785.
Au début de 2020, sur mon ancien lieu de travail, j'ai eu l'occasion de participer à un événement interne de type pwn2own. Plusieurs cibles étaient disponibles, mais celle qui m'intéressait le plus était la caméra de sécurité sans fil Accfly. Malheureusement, je n'ai pas pu terminer mes recherches pour l'événement lui-même, mais comme personne d'autre n'avait tenté de s'attaquer à cet appareil, j'ai poursuivi mes travaux.
L'objectif principal de la recherche était les vulnérabilités pouvant mener à une exécution de code à distance (RCE). Ce type de vulnérabilité permet à un attaquant de prendre le contrôle total de l'appareil et, dans le cas d'une caméra vidéo, peut entraîner une compromission totale de la vie privée du propriétaire. Malheureusement, le firmware de l'appareil s'est avéré truffé de tels problèmes.
Premièrement, l'appareil ne fournit aucune authentification. Par conséquent, un attaquant capable de s'y connecter peut y accéder librement et le reconfigurer. Dans sa forme la plus simple, il est possible de redémarrer l'appareil en continu, le rendant totalement inutilisable pour l'utilisateur légitime. La portée de cette attaque est quelque peu limitée, car l'appareil est conçu pour être utilisé au sein d'un réseau WiFi, généralement derrière un NAT, donc pas directement accessible depuis Internet. Cependant, l'absence de chiffrement entre l'appareil et l'application smartphone de son propriétaire, ainsi que l'utilisation du serveur du fournisseur comme proxy pour la communication, crée une opportunité pour des attaques MitM ou de manipulation DNS, qui peuvent franchir la restriction NAT du WiFi.
De plus, l'application utilise un protocole binaire propriétaire pour la communication. Il a été implémenté dans un mélange de C et de C++ et s'est avéré plein de fonctions de gestion de chaînes non sécurisées. L'exécutable principal contient une énorme quantité de code inutilisé, ce qui suggère qu'il est réutilisé sur d'autres appareils. Cela rend la maintenance plus difficile et augmente la surface d'attaque. L'application n'active aucun mécanisme de sécurité moderne qui la protégerait contre de nombreuses techniques d'exploitation courantes. De plus, elle ne limite même pas les permissions utilisateur, tournant en root - avec les privilèges les plus élevés disponibles.
À la suite de ces recherches, les quatre vulnérabilités suivantes ont été documentées.
CNetClientManage::ServerIP_Proto_Set lors du traitement
des messages entrantsCNetClientTalk::OprMsg lors du traitement
des messages entrantsCNetClientGuard::SubOprMsg lors du traitement
des messages entrantsCFtpProtocol::FtpLogin pendant la procédure de mise à jourPour trois d'entre elles, des exploits RCE ont été développés, permettant à un attaquant d'obtenir le contrôle total de l'appareil. Cependant, en raison de l'absence de réponse du fournisseur aux tentatives de signalement des vulnérabilités, ce dépôt ne contient que des exploits PoC limités, qui ne font que faire planter l'application.
Les problèmes ont été découverts dans la version logicielle V3.10.73 et vérifiés dans la version
logicielle V4.15.77, la dernière disponible au moment de cette publication (26 janvier 2021).
Si vous avez des questions, n'hésitez pas à me contacter par e-mail (voir le commit git) ou via les issues Github. Si vous possédez un appareil IoT qui, selon vous, pourrait être intéressant à pirater, si vous recherchez un chercheur en sécurité ou si vous voulez simplement dire bonjour, je serai ravi de vous lire. Vous pouvez également m'offrir un café !
L'appareil cible est une caméra vidéo contrôlée à partir de l'application mobile associée. Mon analyse a commencé par le trafic réseau de la caméra et s'est poursuivie dans le firmware de la caméra. Un protocole binaire propriétaire est utilisé pour toutes les communications. Les commandes sont soit envoyées directement à l'appareil mobile lorsqu'il est sur le même réseau, soit transmises via le serveur du fabricant de l'appareil. La caméra elle-même écoute sur plusieurs ports TCP (23456,34567) et UDP (34568, 34569). Il n'y a ni chiffrement ni authentification pour le trafic réseau, ce qui permet des attaques MitM ou un accès direct lorsque la caméra est exposée sur le réseau. Il semble probable qu'un accès au flux vidéo soit également possible sans authentification, mais je n'ai pas fait assez de rétro-ingénierie sur le protocole propriétaire pour tenter l'expérience.
Après un bref aperçu de la communication, l'étape suivante consistait à essayer d'accéder au firmware de l'appareil. Ma première tentative a été de le télécharger directement en détournant le processus de mise à jour de l'appareil, mais rien de tel n'est apparu dans le trafic réseau. Je serais resté bloqué à cette étape sans l'aide très précieuse d'un collègue qui a extrait le firmware de la mémoire flash, ce qui m'a permis de poursuivre ces recherches.
Le firmware exécute Linux sur un CPU MIPS little-endian. Il y a exactement un
processus intéressant, appelé Alloca, responsable de la capture vidéo et
qui gère également toutes les communications réseau. L'application est créée en C++ et
contient beaucoup de code inutilisé sur cet appareil. Cela indique que le même logiciel
est également utilisé sur différents appareils.
Même si ce problème a été découvert en dernier, il est crucial pour l'exploitation
effective de la plupart des autres, car ils découlent de l'utilisation de fonctions de chaînes C
non sécurisées. Bien qu'il existe plusieurs techniques pouvant être utilisées pour une
exécution de code réussie dans des scénarios similaires, l'application est créée de
telle manière qu'elles sont pour la plupart inutiles. Le principal problème est que le code
et les données d'Alloca sont alloués statiquement à des adresses basses ( < 0x01000000). Ainsi,
les tentatives de réutilisation de code existant (c'est-à-dire ROP et autres) ne sont pas utiles, car elles
nécessitent la possibilité d'écrire des adresses dans la mémoire du programme. Parce que
les chaînes C utilisent \x00 comme caractère de terminaison et que les fonctions de chaînes
terminent le traitement au premier octet de ce type, il n'est pas possible d'utiliser plus d'un
seul octet NULL. De plus, l'emplacement de la pile est aléatoire et l'application est
fortement multi-threadée, ce qui rend les autres techniques beaucoup moins fiables.
Cette vulnérabilité résulte du partage de données entre plusieurs threads et de
l'utilisation non sécurisée de strcpy. Bien que j'aie analysé ce problème particulier pendant
longtemps, je n'ai repéré l'opportunité de l'utiliser comme vecteur de fuite de données que
quelques semaines avant cette publication. Fait intéressant, grâce à la fuite de
l'adresse du tas d'un objet C++, cette vulnérabilité permet également une exécution de code à
distance. Cependant, cette attaque n'est pas incluse dans ce rapport.
L'application Alloca peut se mettre à jour via FTP. Cette opération peut être
demandée par un serveur, qui fournit également le nom d'utilisateur, le mot de passe et le
nom de fichier nécessaires. La fonction qui initie la mise à jour est présentée ici :

Trois appels à strcpy sont évidemment non sécurisés et conduisent à un débordement de tas, car l'
objet ftpUpgrade est alloué dynamiquement. Malheureusement, l'ordre dans lequel
les copies sont effectuées et la disposition de la structure ftpUpgrade rendent impossible de
démarrer réellement un thread qui fuirait des données. En regardant de plus près, le paquet entrant
révèle la structure suivante :
struct ftp_upgrade_pkt {
struct pktHeader;
char username[16];
char password[16];
char filename[128];
}
tandis que l'objet ftpUpgrade ressemble à peu près à ceci :
struct CNetClientFtpUpgrade {
// ... something here
char filename[128];
char unknown[6];
char username[16];
char password[16];
CFtpDownlad *;
CNetClientConnect*;
int something[5];
bool threadRunning;
// ... and more
}
La fuite peut se produire après que l'un des pointeurs internes (CFtpDownload*,
CNetClientConnect*) a été rempli par l'application. De plus, le
nom d'utilisateur et le mot de passe sont copiés (une fois de plus, mais de manière sécurisée cette fois) vers l'
objet nouvellement créé avant que son pointeur ne soit stocké dans l'emplacement fuyable,
de sorte que la fuite ne peut concerner que filename. Par conséquent, le nom de fichier doit
être très long, mais en raison de l'ordre et du comportement de terminaison de strcpy, un
nom de fichier suffisamment long entraînera un nom d'utilisateur et un mot de passe encore plus longs,
qui finiront par écraser threadRunning et ne démarreront aucun thread.
Si ce code était mono-threadé, on ne pourrait pas faire grand-chose. Mais comme le nouveau
thread FtpDownload est créé et exécute la fonction DownloadFile, il présente
une opportunité intéressante car il partage l'objet CNetClientFtpUpgrade avec
le thread qui traite les paquets entrants. Non seulement il possède plusieurs opérations d'E/S
contrôlables de l'extérieur (requêtes DNS, traitement de la connexion FTP),
mais il essaie également de se connecter au FTP jusqu'à 10 fois (ceci est fait dans
l'appelant de DownloadFile). Cela permet de contrôler l'exécution du thread
FtpDownload (en le bloquant sur des opérations d'E/S), donnant ainsi le temps au
thread de traitement des messages de traiter d'autres requêtes.

En bref, rien qu'en envoyant plusieurs requêtes de mise à jour, il est possible de modifier le
filename (et d'autres paramètres) utilisé par le thread FtpDownload déjà en cours
et de recevoir l'adresse du tas divulguée. En prime, la fonction FtpSize (marquée en
vert) utilise le tampon situé à l'intérieur de l'objet référencé par l'adresse divulguée pour stocker le
filename lui-même, ce qui permet une injection triviale du premier étage de shellcode. La
seule limitation ici est la longueur et l'absence d'octets NULL, en raison de l'utilisation de strcpy.
Un PoC d'exemple est fourni, qui ne fait que divulguer une adresse du tas de l'appareil.
débordement de tampon basé sur la pile sans authentification dans la fonction CNetClientManage::ServerIP_Proto_Set
L'absence totale d'authentification dans le traitement du trafic entrant m'a poussé à rechercher
les gestionnaires de paquets. L'une des fonctions intéressantes est ServerIP_Proto_Set.
Elle semble être utilisée pour créer une réécriture statique de la résolution DNS. Je n'ai
pas trouvé de moyen de rediriger le trafic de cette façon, mais il y a un autre débordement
de tampon ici (marqué en orange).

Les données, directement lues depuis le paquet, sont utilisées dans la fonction sprintf.
Dans ce cas, on suppose que les données du paquet tiendront
dans un tampon de 16 octets, mais l'utilisation du format simple %s permet d'écrire autant
d'octets que l'on souhaite, à condition qu'ils ne contiennent pas de NULL.
Cette vulnérabilité est plutôt limitée. Bien qu'il soit possible d'écrire beaucoup de
données sur la pile, de sorte qu'un NOP-sledge pourrait fonctionner, il n'est pas possible d'écrire des
octets NULL. Même tenter d'écrire un seul octet NULL échouera, car le format de la
fonction sprintf le fait précéder d'un \n. Un autre obstacle est un objet CMutex
stocké après le tampon. Toute tentative de débordement doit remplir ce
mutex avec une valeur correcte (ou au moins une qui satisfera le destructeur de CGuard
pthread_mutex_unlock.
Après quelques tests, j'ai découvert qu'un tampon rempli de NULL suffit
pour revenir correctement de pthread_mutex_unlock, mais il devait encore être
déréférencé vers une adresse mémoire valide.La fuite vient à la rescousse. L'attaque est un peu complexe, car nous avons besoin d'une adresse du tas qui ne contienne aucun octet NULL. Heureusement, la recherche est facilitée, car l'appareil nous offre la possibilité d'effectuer un redémarrage à distance sans authentification. Chaque fois, une allocation d'espace d'adressage du tas différente est fournie. Il est donc possible de simplement réinitialiser l'appareil et de divulguer une adresse jusqu'à ce qu'une adresse appropriée soit trouvée. Pratiquement, cela permet également de stocker un court premier étage du shellcode. Comme nous devons contourner un problème de mutex (un pointeur vers un pointeur est nécessaire), nous divulguons une autre adresse, en passant cette fois une adresse préalablement divulguée comme nom de fichier. Le schéma suivant montre la disposition mémoire attendue :

Si tout se passe comme prévu, il est possible de passer une 2e adresse comme mutex et la première comme adresse de retour. Cependant, cela n'est pas nécessaire pour simplement faire planter une application comme le fait le PoC.
débordement de tampon basé sur le tas sans authentification dans la fonction CNetClientTalk::OprMsg
L'appareil est censé permettre une communication vocale bidirectionnelle. Un autre
gestionnaire de paquets entrants semble être responsable de la réception et de la lecture
de l'audio. Le paquet réseau audio_pkt_hdr est décrit par la structure suivante :
struct audio_pkt_hdr {
struct pktHeader field_0x0;
int field_0x14
int field_0x18
int field_0x1c
char audioBuff[0x140];
}
L'un des champs de la structure pktHeader est la longueur du paquet (telle
qu'elle est transférée sur le réseau). Ce champ peut être librement défini par l'expéditeur. La
partie vulnérable consiste à copier des données directement depuis le paquet entrant en utilisant une valeur
de longueur non fiable fournie dans l'en-tête du paquet entrant.

Comme on peut le voir, l'objet CNetClientTalk est créé avec le constructeur suivant :

L'appel ci-dessus à memcpy entraîne donc un débordement de tampon sur le tas.
Malheureusement, l'exploitation réelle de ce problème est plutôt difficile. Même s'il est
possible d'écraser le tas à plusieurs reprises, je n'ai pas trouvé de moyen de contrôler
les données qui seront stockées sur le tas après le tampon débordant. Comme
l'application compte plus de 50 threads actifs, dont certains sont responsables du
traitement de l'audio et de la vidéo, elle alloue et désalloue constamment de la mémoire. Cela
entraîne une modification constante des données du tas, ce qui rend difficile la prédiction de ce qui est
stocké après le tampon et son écrasement correct.
débordement de tampon basé sur la pile sans authentification dans la fonction CNetClientGuard::SubOprMsg
Voici un autre gestionnaire de paquets entrants. Cette fois, le paquet réseau a la structure suivante (l'en-tête de paquet commun est omis) :
struct pkt_hdr_sub_cliGuard {
dword deviceId;
dword userId;
dword magic;
dword subCmd;
dword field_0x10;
dword field_0x14;
dword field_0x18;
dword field_0x1c;
dword guard_icommand;
dword moreThenRandomStackValue;
dword itemCnt;
char array_of_0x18[24];
};
Encore une fois, la partie intéressante est le dernier tableau (car nous pouvons agrandir ce paquet autant
que nous le souhaitons), qui contient une structure interne de taille 24. La
vulnérabilité découle de l'hypothèse que le itemCnt reçu ne dépassera pas
6, car le tampon de destination de la copie a une taille de 144 (=24*6), ce qui
est visible sur la liste suivante (surbrillance orange) :

Cette fois, la copie est effectuée à l'aide de memcpy (surbrillance verte), il n'y a donc
aucune limite sur les caractères autorisés. La copie se fait par morceaux, dans une boucle while (marquée
en bleu). Il convient de noter que le compteur cnt_v0 diminue dans une
boucle, de sorte que les morceaux sont copiés en ordre inverse. En incluant les variables qui suivent
le tampon vulnérable buf, le débordement doit avoir 256 octets, puis 4 registres
($s0-$s3) et $ra. Parce que nous n'avons aucune connaissance de la disposition mémoire,
le code PoC utilise une technique ROP. Un seul gadget est utilisé,
qui joue l'un des sons intégrés de l'appareil (et plante).
débordement de tampon basé sur la pile sans authentification dans la fonction CFtpProtocol::FtpLogin
L'une des premières directions de mon analyse était de rechercher la procédure de mise à jour.
Comme je l'ai découvert, l'appareil dispose d'une fonctionnalité de mise à jour FTP, qui peut être
initiée en envoyant une demande de mise à jour et aboutit au téléchargement du firmware depuis le
site FTP externe. Comme pour les autres vulnérabilités, il n'est pas nécessaire de
s'authentifier avant de demander la mise à jour de l'appareil. L'analyse approfondie de la fonctionnalité FTP
a révélé un débordement de tampon basé sur la pile dans la fonction
CFtpProtocol::FtpLogin. Comme on peut le voir dans la liste décompilée
ci-dessous, la fonction passe un tableau char de taille 256 à la fonction FtpPwd.

FtpPwd est utilisée pour obtenir le répertoire de travail courant du serveur FTP. Elle charge
son tampon interne avec jusqu'à 1500 octets de réponse, puis les copie dans
le tampon fourni. Cette séquence d'appels entraîne un débordement de 1242 octets. Dans
ce cas, les caractères autorisés sont très limités, car l'utilisation de " (double-guillemet)
raccourcirait la chaîne d'entrée (strchr est utilisé pour rechercher un
char dans une chaîne C) et ne ferait pas déborder le tampon. Heureusement, il est seulement
nécessaire de fournir une adresse unique vers laquelle l'exécution du code sera
redirigée.

Pour exploiter cette vulnérabilité, il faut soit contrôler le DNS, soit rediriger (ou faire un MitM) la connexion vers le serveur FTP. L'application ne dispose d'aucune protection moderne, il est donc possible d'exécuter le code directement depuis la pile. Sans fuite d'adresse, le mieux que l'on puisse faire est soit de deviner l'emplacement de la pile, soit de rediriger l'exécution vers une seule fonction, qui fera ensuite planter l'application. Mes premières tentatives consistaient exactement à cela, jouer l'un des sons intégrés, ce qui est fourni comme PoC. En utilisant la fuite, il est possible d'obtenir le contrôle total de l'appareil.