Fetch the Flag CTF 2022 : solution de Roadrunner
Giovanni Funchal
10 novembre 2022
0 minutes de lectureMerci d’avoir participé à Fetch avec nous ! Félicitations aux milliers de joueurs qui nous ont rejoints pour le Fetch the Flag CTF. Et un immense merci aux Snykers qui ont créé, testé et présenté les défis !
Si vous aimez les escape games, vous allez adorer le défi Roadrunner du concours Fetch the Flag 2022 de Snyk ! Dans cet article, je vais vous expliquer comment j’ai abordé et résolu le défi en exploitant une vulnérabilité de validation des entrées dans le code, qui nous permet de sortir d’un bac à sable.
Le défi
Pouvez-vous semer un grand géocoucou ? Même pas en rêve, José !
Ce défi commence par nous provoquer. Allons-nous trouver le flag ?
Pour commencer, nous avons accès à une page Web où se trouve une sorte d’éditeur de code en ligne. Ce type d’éditeur est souvent utilisé pour apprendre ou faire des essais sans avoir à installer une chaîne d’outils pour un langage de programmation. Le langage Go en propose même un officiel.
Nous pouvons saisir du code Go et cliquer sur Run. Le code est envoyé à un backend, qui l’exécute et renvoie le résultat, lequel s’affiche ensuite sur la page Web.
Nous avons également accès au code source Go du service backend qui exécute cette page Web (roadrunner.go), ainsi qu’au Dockerfile utilisé pour empaqueter ce service.
Notre première étape consiste à examiner les indices qui nous ont été fournis et à explorer un peu.
Guide pas à pas
Exploration
La curiosité est généralement un bon atout pour résoudre ces défis. La première chose que j’ai faite en commençant, c’est donc d’explorer et d’essayer quelques trucs. Je ne programme pas en Go, alors j’ai cherché un exemple « hello world » en ligne, je l’ai collé dans la page Web, j’ai cliqué sur Run et j’ai obtenu un « hello world » en retour. Jusqu’ici, tout va bien.

Cet exemple « hello world » utilise la bibliothèque fmt pour afficher un message dans la sortie.
J’ai ensuite examiné le code source de la page Web dans mon navigateur. J’ai remarqué que le bouton Run était associé à une action submit dans un formulaire.

J’ai ensuite examiné le gestionnaire JavaScript, qui envoie une requête POST au point de terminaison /run du backend, puis renseigne le contenu de la zone de résultat à partir de la réponse, comme prévu.

Je me suis ensuite intéressé au code source du backend fourni avec le défi. Commençons par le Dockerfile :

Nous avons trouvé le flag ! On voit que l’image déployée contient un fichier flag.txt à la racine. Il ne nous reste plus qu’à le récupérer. Nous devons trouver un moyen de lire le contenu de ce fichier.
Une piste à suivre
Examinons roadrunner.go. La fonction main est toujours un bon point de départ.

On voit ici les deux points de terminaison, / et /run. Commençons par examiner /, qui est géré par la fonction welcome.

Le code utilise ici la bibliothèque Go html/template pour générer le code HTML de la page Web en lisant le fichier index.html et en interpolant des attributs. Intéressant.
Examinons maintenant le point de terminaison /run, géré par la fonction runner.

Plusieurs éléments intéressants se passent ici. Le code crée d’abord un bac à sable, récupère le script saisi dans la requête, puis l’écrit dans un fichier du bac à sable. Vient ensuite une étape de nettoyage du script, qui tente de vérifier que celui-ci peut être exécuté sans risque et peut rejeter l’entrée. Si l’entrée est acceptée, le script est exécuté, puis le résultat est récupéré et renvoyé dans la réponse.
En examinant ce code, j’ai d’abord remarqué que le champ dirname de la structure Sandbox est lui aussi décodé à partir du JSON de la requête et peut donc être contrôlé par l’utilisateur. Ce champ sert ensuite à créer un chemin pour écrire un fichier, ce qui constitue une vulnérabilité. Cependant, je ne savais pas comment l’exploiter ; j’ai donc décidé de me concentrer à nouveau sur l’étape de nettoyage. Examinons de plus près sanitizeScript.

Ce code analyse le script fourni, puis examine ses imports. Une liste de blocage contient les bibliothèques Go les plus courantes pour manipuler des fichiers. Si notre script importe l’une d’elles, il est rejeté avec un message amusant.
N’oubliez pas que notre objectif est de lire le contenu du fichier /flag.txt.
Ma première idée a été que cette liste n’était peut-être pas exhaustive, ou qu’il existait d’autres moyens de lire des fichiers. Comme je l’ai indiqué plus tôt, je ne programme pas en Go ; j’ai donc fait quelques recherches en ligne.
La révélation
Vous souvenez-vous d’un autre endroit où nous avions lu des fichiers ?

Eh oui, c’est encore la fonction welcome. Elle lit clairement un fichier et utilise la bibliothèque de templates, qui ne figure pas dans la liste de blocage de sanitizeScript. Peut-être pouvons-nous l’utiliser ?
J’ai donc consulté la documentation de cette bibliothèque. Après avoir créé un objet Template, on exécute le template, qui écrit le résultat dans le writer.

Nous devons convertir l’interface io.Writer en chaîne de caractères pour pouvoir ensuite l’afficher. Après quelques recherches en ligne, j’ai découvert que je pouvais utiliser strings.Builder (la bibliothèque strings ne figure pas non plus dans la liste de blocage).
Voici donc notre solution finale :

L’exécution de cet extrait affiche tout le contenu du fichier flag.txt. Mission accomplie !
Pour conclure sur Roadrunner
Alors, qu’avons-nous appris ? Nous avons utilisé une entrée contaminée soigneusement construite pour exploiter une vulnérabilité de validation des entrées dans un éditeur de code en ligne. Une faille dans le nettoyage du script nous a permis de sortir du bac à sable. Comprendre le fonctionnement interne de l’application nous a aidés à passer très rapidement de l’idée à l’exploitation. Il ne s’agissait que d’un défi CTF. Dans un contexte réel, la création de ce type d’exploit pourrait demander quelques tâtonnements et essais.
Snyk Code — un outil gratuit de test de sécurité statique des applications (SAST) — peut vous aider à détecter et à corriger les vulnérabilités de validation des entrées dans votre code.
D’autres techniques, comme la défense en profondeur, peuvent également rendre cette exploitation plus difficile. Par exemple, la fonction runScript pourrait être modifiée pour exécuter le script avec un autre utilisateur disposant de permissions réduites. Dans ce cas, la vulnérabilité n’aurait pas pu être exploitée seule, à moins d’être combinée à une seconde vulnérabilité, comme une élévation de privilèges.
L’exécution sécurisée de code non fiable dans un bac à sable est notoirement difficile. Les bacs à sable sont conçus pour empêcher les applications d’accéder à d’autres ressources et données. En pratique, mieux vaut éviter complètement d’exécuter du code non fiable, si possible.
Et voilà, c’est tout ! J’espère que vous avez apprécié. Vous voulez savoir comment nous avons trouvé tous les autres flags ? Consultez notre page solutions Fetch the Flag pour découvrir comment nous avons procédé.
Lancez-vous dans les compétitions Capture The Flag
Apprenez à résoudre des défis Capture The Flag en regardant à la demande notre atelier virtuel d’initiation.



