Skip to main content

Fetch the Flag CTF 2022 : solution de Roadrunner

Écrit par

Giovanni Funchal

feature ctf roadrunner

10 novembre 2022

0 minutes de lecture

Merci 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.

Environnement de test Roadrunner affichant du code Go qui imprime « hello world », avec un bouton Run et un panneau de sortie affichant « hello world »

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.

Extrait de code HTML montrant un formulaire avec un libellé « Roadrunner Sand… » et un bouton Run

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.

Code JavaScript qui gère l’envoi d’un formulaire et transmet des données de script à un point de terminaison /run via une requête POST

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

Code Dockerfile présentant les arguments de version de Debian et Go, les commandes d’installation des paquets et la copie de flag.txt dans le répertoire de l’application

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.

Extrait de code Go montrant un serveur Roadrunner qui démarre et écoute sur un port local

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

Éditeur de code affichant une fonction d’accueil en Go qui analyse et exécute un modèle index.html.

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.

Éditeur de code en mode sombre affichant un gestionnaire HTTP en Go qui décode une requête, assainit et exécute un script, puis écrit le résultat.

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.

Code Go pour sanitizeScript, qui bloque les manipulations de fichiers, les appels système et les imports réseau.

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 ?

Extrait de code montrant un gestionnaire d’accueil en Go qui analyse et exécute un modèle index.html.

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.

Documentation du code montrant la méthode Go `Execute` d’un `Template`, avec ses paramètres writer, data et error

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 :

Capture d’écran intitulée « Roadrunner Sandbox » montrant du code Go qui analyse un modèle flag.txt et affiche le résultat généré.

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.

Lire la suite

Blog

Les modèles de pointe ont trouvé les vulnérabilités. Seul l’attaquant a trouvé les chaînes d’exploitation.

L’analyse statique a détecté les failles, mais seuls des tests d’attaque en conditions réelles ont prouvé comment elles pouvaient être enchaînées pour provoquer des compromissions. Comparaison d’Evo COS, de Claude Security et de Claude Code Security.

feature insights context
Blog

Les attaques autonomes sont déjà là. La défense doit suivre leur rythme.

Les attaquants autonomes réduisent la fenêtre de défense. Découvrez comment la découverte, la correction, la validation et la prévention continues peuvent aider les équipes de sécurité à suivre le rythme.

Blog

Pourquoi les agents de codage IA créent-ils sans cesse des failles de contrôle d’accès ?

Les agents de codage IA peuvent générer une logique d’autorisation qui compile et passe la revue, tout en exposant les données d’un tenant à un autre. Découvrez pourquoi les failles de contrôle d’accès sont difficiles à détecter et comment les prévenir.