Explorer 3 types de vulnérabilités de traversée de répertoires en C/C++
Kirill Efimov
4 avril 2022
0 minutes de lectureLes vulnérabilités de traversée de répertoires (également appelées vulnérabilités de traversée de chemins) permettent à des acteurs malveillants d’accéder à des dossiers qui devraient leur être inaccessibles. Dans cet article, nous allons voir comment fonctionnent ces vulnérabilités sur les serveurs web écrits en C/C++, et comment les prévenir.
Pour notre équipe de recherche en sécurité, les écosystèmes C et C++ sont restés longtemps hors de notre champ d’action. Cela a changé lorsque FossID a rejoint Snyk. FossID était spécialisé en C/C++, ce qui nous a permis de lancer plusieurs projets afin de mieux comprendre la posture globale de sécurité de l’information pour ces langages. Nous avons ainsi découvert de nombreuses vulnérabilités de gravité élevée et sommes impatients de vous présenter nos résultats.
Dans cet article, nous allons nous concentrer sur la restriction inadéquate d’un chemin d’accès à un répertoire restreint (CWE-22). Cette faiblesse prend de nombreuses formes et, selon le Top 25 des faiblesses logicielles les plus dangereuses de CWE, elle restera très répandue et dangereuse en 2022. Nous allons examiner trois types de traversée de répertoires et associer les vulnérabilités correspondantes à des exemples de code vulnérable, des correctifs et leurs conséquences possibles.
Lecture arbitraire de fichiers
Les serveurs web permettent souvent de diffuser des ressources statiques comme des fichiers HTML, CSS et JS. Ces ressources sont généralement stockées dans des dossiers dédiés (par exemple /static, /www ou /assets) dans le système de fichiers. Une vulnérabilité de lecture arbitraire de fichiers survient lorsque l’application web ne nettoie pas correctement le chemin d’accès au fichier statique, ce qui permet à l’utilisateur d’utiliser des segments de chemin « ../ » pour sortir du dossier prévu et, à terme, lire n’importe quel fichier sur le disque.
Passons directement à un exemple de cette vulnérabilité que nous avons récemment découverte dans le framework web Crow. Crow est un microframework C++ permettant d’exécuter des services web.
Pour cet exemple, nous allons créer une simple application « hello world », composée d’un seul fichier main.cpp :
Pour compiler l’exemple, nous utilisons Ubuntu : g++ -pthread -o server main.cpp. Nous pouvons ensuite lancer le serveur en exécutant ./server dans le terminal.
D’après la documentation, Crow diffuse par défaut les fichiers statiques depuis le dossier /static, situé au même emplacement que le fichier exécutable server. Pour le vérifier, créons le dossier /static et un fichier texte à l’intérieur : mkdir static && echo “test” > static/test.txt
Depuis une autre fenêtre de terminal, nous pouvons maintenant exécuter curl pour vérifier que tout fonctionne :
La commande curl http://localhost:8000/ renvoie Hello world!.
La commande curl http://localhost:8000/static/test.txt affiche test, qui correspond bien au contenu du fichier static/test.txt.
Pour exploiter la vulnérabilité, nous pouvons exécuter curl --path-as-is "http://localhost:8000/static/../../etc/passwd". La réponse affichera le contenu du fichier /etc/passwd. L’astuce réside dans l’option --path-as-is, qui désactive la normalisation des chemins dans les URL. Le serveur reçoit donc exactement l’URL fournie, segments « ../ » compris.
Conséquences en matière de sécurité
La fuite de fichiers arbitraires depuis un serveur de production constitue généralement un problème critique : les clés SSH, divers identifiants et le code source du serveur peuvent permettre à un acteur malveillant d’intensifier son attaque et de prendre le contrôle du serveur ou de données confidentielles d’utilisateurs.
Il en va de même pour les appareils embarqués, qui utilisent souvent des serveurs C++. Cette vulnérabilité de traversée de répertoires est fréquente sur les routeurs Wi-Fi : NETGEAR, Belkin, TP-Link, entre autres. Dans ce cas, elle pourrait permettre de dérober les identifiants du panneau d’administration et de prendre le contrôle de tout le réseau local.
Dans certains cas, la traversée de répertoires peut être dangereuse même si le serveur vulnérable s’exécute localement sur votre machine. Les serveurs web locaux sont souvent utilisés dans des applications hybrides web et de bureau, comme Electron. Pour en savoir plus sur ces vecteurs d’attaque, consultez notre étude sur les extensions vulnérables de VS Code.
Mesures d’atténuation
Crow v0.3+4 a corrigé cette vulnérabilité de traversée de répertoires. Le problème a été introduit dans le fichier app.h : le code concaténait simplement une partie de l’URL au chemin du dossier static, puis appelait la méthode set_static_file_info pour diffuser le fichier :
Dans ce cas, le correctif consistait à ajouter un appel à utility::sanitize_filename(path) au début de la méthode set_static_file_info. sanitize_filename remplace toutes les occurrences de « .. » par « _ », ce qui corrige la vulnérabilité. Le correctif n’est pas parfait, car le serveur ne pourra plus diffuser les fichiers dont le nom contient « .. », mais ces cas sont probablement rares et ne devraient pas poser de problème majeur.
Écriture arbitraire de fichiers
Les vulnérabilités d’écriture arbitraire de fichiers sont très courantes dans les bases de code où l’application permet d’envoyer des fichiers. La cause fondamentale est très similaire au cas précédent, mais un peu plus difficile à repérer. Imaginons un simple formulaire HTML d’envoi de fichiers :
Essayons de sélectionner un fichier texte nommé « test.txt » et contenant « test ». Le navigateur génère une requête HTTP semblable à celle-ci :
En examinant le corps de la requête de plus près, vous verrez qu’il contient plus que le seul contenu du fichier. Il comporte notamment un champ de nom de fichier, qui peut contenir des segments « ../ » et que le serveur doit traiter correctement.
Pour illustrer le problème, nous allons utiliser une vulnérabilité d’écriture arbitraire récemment découverte dans le très populaire framework de serveur web embarqué Mongoose.
L’exemple d’envoi de fichiers du dépôt Mongoose permet aux utilisateurs d’envoyer des fichiers par blocs, ce qui est très pratique lorsque l’appareil qui héberge le serveur dispose de peu de RAM. Pour compiler et exécuter l’exemple, il suffit de cloner le dépôt et de lancer la commande make dans le dossier de l’exemple. Le serveur se met immédiatement à écouter sur le port 8000 et définit /tmp comme dossier de destination des fichiers envoyés (comme indiqué à la ligne 13 du fichier main.c.)
Nous pouvons maintenant utiliser curl pour envoyer des fichiers. La commande curl -X POST --data-binary "test" http://localhost:8000/upload?offset=0&name=test.txt crée sur le serveur un fichier /tmp/test.txt contenant le texte « test ».
Pour exploiter la vulnérabilité, nous pouvons ajouter des segments de chemin « ../ » juste avant le paramètre de requête name : curl -X POST --data-binary "pwned" http://localhost:8000/upload?offset=0&name=../malicious-file. L’exploitation crée alors un fichier malicious-file à la racine du système de fichiers.
Conséquences en matière de sécurité
L’exploitation des vulnérabilités d’écriture arbitraire de fichiers n’est pas aussi simple que celle des vulnérabilités de lecture arbitraire, mais elle peut tout de même, dans de nombreux cas, conduire à une exécution de code à distance (RCE). Cela est souvent possible parce qu’un acteur malveillant peut écraser des fichiers système importants comme /etc/init.d/, voire les fichiers exécutables du serveur.
Mesures d’atténuation
Comme dans l’exemple précédent, Mongoose a corrigé la vulnérabilité en supprimant « .. » du nom de fichier fourni par l’utilisateur. Le correctif est inclus dans la version 7.6.
De manière générale, nous vous recommandons d’éviter les noms de fichiers fournis par les utilisateurs et d’utiliser plutôt des chaînes aléatoires, des horodatages ou des hachages MD5. Cette recommandation s’applique souvent aux applications qui permettent d’envoyer des fichiers et aide à prévenir de nombreuses autres vulnérabilités, comme les XSS.
Zip Slip
Le troisième type de traversée de répertoires que nous allons aborder aujourd’hui est Zip Slip. Comme son nom l’indique, il s’agit de vulnérabilités liées à la logique d’extraction d’archives. Ce type de vulnérabilité est très proche de l’écriture arbitraire de fichiers, mais sa surface d’attaque est plus limitée, car les mécanismes d’extraction d’archives sont moins courants.
Pour illustrer la vulnérabilité Zip Slip, nous allons utiliser la version vulnérable 6.1.4 de JUCE, un framework open source multiplateforme pour les applications C++.
Il s’agit d’une application très simple qui utilise l’API JUCE pour extraire archive.zip dans le dossier data.
Pour voir comment cela fonctionne, nous pouvons créer une archive contenant un fichier test.txt :
Si nous compilons et exécutons notre application d’exemple, le dossier data sera créé et le fichier test.txt s’y trouvera. Mais que se passe-t-il si nous créons une archive contenant un fichier situé un niveau plus haut dans l’arborescence ?
Dans ce cas, si nous exécutons le programme, le fichier bad.txt se trouvera en dehors du dossier cible data. Comme vous pouvez le constater, les risques liés à Zip Slip sont exactement les mêmes que ceux de l’écriture arbitraire de fichiers.
Il existe d’autres variantes de Zip Slip, comme l’extraction de liens symboliques. Nous vous recommandons de lire notre article de recherche pour approfondir le sujet.
Mesures d’atténuation
La vulnérabilité présentée a été corrigée dans la version 6.1.5 du framework JUCE, en même temps qu’une autre variante : Zip Slip via un lien symbolique. JUCE a corrigé la vulnérabilité en vérifiant que le chemin complet du fichier résolu reste bien dans le répertoire cible.
Conseils généraux pour remédier aux vulnérabilités
En étudiant cette catégorie de vulnérabilités, l’équipe de recherche en sécurité de Snyk a examiné une multitude d’exemples de code mettant en œuvre diverses logiques liées aux chemins du système de fichiers. Nous pensons avoir trouvé une règle générale qui peut vous aider à vous protéger contre les vulnérabilités de traversée de répertoires.
Si vous pouvez éviter d’utiliser des chemins de fichiers fournis par l’utilisateur, faites-le !
Cette règle s’applique à de nombreux mécanismes d’envoi de fichiers et d’extraction d’archives.
Si vous devez utiliser des chemins de fichiers fournis par l’utilisateur, nous vous recommandons de suivre la logique ci-dessous (implémentée avec std) :
Le code ci-dessus nécessite au minimum C++17, mais la même logique peut être implémentée avec boost. Si ni std ni boost ne sont disponibles dans votre environnement, il suffit également de remplacer « .. » par des chaînes vides, puis de réduire les suites de plusieurs caractères « / » à un seul.
Par ailleurs, si votre application est multiplateforme et doit aussi fonctionner sous Windows, pensez à normaliser les caractères séparateurs de répertoires. Dans la plupart des cas, Windows accepte aussi bien la barre oblique (« / ») que la barre oblique inversée (« \ ») comme séparateur. La séquence « ..\ » équivaut donc à « ../ » et peut également servir à effectuer une attaque par traversée de répertoires. Le plus simple pour sécuriser votre code sous Windows consiste à remplacer « / » par « \ » avant de traiter les chemins.
Conclusion et principaux enseignements
Lorsqu’ils pensent aux vulnérabilités en C et C++, les spécialistes de la sécurité de l’information se concentrent souvent sur des problèmes de bas niveau, comme les différentes formes de corruption de mémoire. Bien que ces problèmes aient un impact majeur sur l’écosystème, nous devons également tenir compte du contexte applicatif et prévenir les vulnérabilités de haut niveau, en plus de celles de bas niveau. Au début de nos recherches, nous pensions que les développeurs web en C/C++ ne prêtaient pas suffisamment attention aux problèmes web courants. Nos découvertes de nombreuses vulnérabilités de traversée de répertoires l’ont confirmé :
CVE-2022-25299 : écriture arbitraire de fichiers dans Mongoose, un serveur web embarqué.
CVE-2022-25297 : écriture arbitraire de fichiers dans Drogon, un framework d’applications HTTP basé sur C++14/17.
CVE-2021-23520, CVE-2021-23521 : Zip Slip dans JUCE, un framework open source multiplateforme pour les applications C++.
CVE-2021-23514 : traversée de chemins dans Crow, un microframework C++.
CVE-2022-25298 : traversée de chemins dans Webcc, un client et serveur HTTP léger en C++.
Cette liste continue de s’allonger. Nous la mettrons à jour au fur et à mesure que les contributeurs publieront leurs correctifs.
Restez en sécurité !
Lancez-vous dans les challenges Capture The Flag
Apprenez à résoudre des challenges Capture The Flag en regardant à la demande notre atelier virtuel d’initiation.
