Compte rendu du CTF Fetch the Flag 2022 : Disposable Message
Michael Aquilina
10 novembre 2022
0 minutes de lectureMerci d’avoir joué à Fetch avec nous ! Félicitations aux milliers de participants qui nous ont rejoints pour leFetch the Flag CTF. Et un grand merci aux Snykers qui ont créé, testé et documenté les défis !
Dans cet article, nous allons parler du défi Disposable Message de l’événement Fetch the Flag CTF 2022 de Snyk. Pour relever ce défi, il fallait exploiter une page Web vulnérable à l’aide de techniques d’injection CSS et récupérer le flag. Disposable Message s’est révélé particulièrement difficile en raison d’une politique de sécurité du contenu (CSP) stricte. La clé pour contourner cette politique a été de tirer parti de l’expiration des messages après leur consultation.
Cet article explique comment j’ai trouvé un exploit fonctionnel pour ce défi. Je vais vous raconter mon parcours, en commençant par la phase d’investigation, puis en expliquant comment j’ai utilisé mes découvertes pour concevoir un exploit fonctionnel. Le code de l’exploit a été écrit en Python ; je partirai du principe que vous en connaissez les bases. Des connaissances en CSS, JavaScript et HTML seront également utiles, mais je vais expliquer la plupart des éléments abordés.
Investigation
Le défi commence par une très courte description : « Ce message a expiré. », accompagnée d’un lien vers une page Web.
La description indique que la page Web nous permet de créer des messages qui ne peuvent être consultés qu’une seule fois avant d’être supprimés par le serveur. Le fait que cette information figure dans la description laisse probablement fortement penser qu’elle jouera un rôle (gardez cela à l’esprit pour la suite).
En accédant à la page, nous découvrons ceci :

La page comporte une zone de texte permettant de rédiger des messages. Si nous écrivons un message et cliquons sur le bouton Send!, la page suivante s’affiche :

Nous voyons une nouvelle URL de la forme /view/<message-id> (nous l’appellerons désormais la page de consultation du message). Nous voyons également un bouton permettant de copier ce message dans le presse-papiers et un bouton Ask admin bot to visit. Si nous ouvrons l’URL de consultation du message dans notre navigateur, nous voyons le message créé. Si nous consultons la même page une deuxième fois, le serveur Web renvoie un code d’état 404.

Revenons au bouton Ask Admin bot to visit. On peut supposer qu’un clic demande à un bot d’administration distant de consulter le message dans un navigateur. Nous en sommes presque certains, car le message expire peu après le clic sur le bouton. Le nom « admin bot » suggère aussi fortement que ce navigateur contient le flag que nous recherchons.
Examinons ensuite le code source de la page pour voir ce qui ressort.
Emplacement du flag
L’extrait de code HTML suivant se trouve sur la page de consultation du message :
D’après le commentaire du code, l’attribut data-flag de cet élément div sera renseigné à partir de nos cookies. Nous pouvons facilement le vérifier en ouvrant les outils de développement de notre navigateur et en modifiant nos cookies pour voir ce qui se passe.
Le nom le plus évident pour le cookie contenant le flag serait « flag ». Si nous définissons la valeur « SNYK{hello-world} » dans un cookie nommé « flag », puis ouvrons à nouveau la page de consultation du message, nous constatons que l’attribut est correctement renseigné :

Injection CSS
En examinant le script intégré à la page de consultation du message, nous trouvons également le code JavaScript suivant :
Le script vérifie si l’URL contient un paramètre de chaîne de requête « color ». Si c’est le cas, la valeur de ce paramètre est insérée dans une balise style. Enfin, cette balise style est ajoutée dynamiquement à notre page Web.
Il est intéressant de noter que cette balise style est construite à l’aide du formatage de chaînes de caractères. Celui-ci nous permet de sortir de la balise style et de la prolonger pour y insérer n’importe quelle valeur CSS. C’est ce qu’on appelle une vulnérabilité d’injection CSS.
Nous pouvons facilement vérifier si cette page est vulnérable à l’injection CSS en définissant le paramètre color sur une valeur comme color=00ff00;font-size:80px et en vérifiant que la taille de la police change bien comme prévu :

L’injection CSS nous permet d’exfiltrer des informations de la page affichée dans le navigateur d’un utilisateur. Cette attaque consiste à créer plusieurs sélecteurs CSS qui déclenchent des URL lorsque des valeurs correspondent à des sélecteurs spécifiques.
Par exemple, si nous voulions récupérer l’attribut data-flag d’un élément HTML div, nous pourrions définir le paramètre color sur une valeur comme :
Le ^= ci-dessus déclenchera l’URL associée si la valeur de l’attribut commence par la chaîne spécifiée.
Cela signifie que si, par exemple, l’attribut data-flag de l’élément div commence par a, notre navigateur consultera http//evil.com/a . Si la valeur commence par b, il consultera http//evil.com/b, et ainsi de suite…
Si le serveur evil.com nous appartient, nous saurons quelles pages ont été consultées. En essayant suffisamment de sélecteurs CSS, nous pouvons extraire l’attribut data-flag caractère par caractère.
Politique de sécurité du contenu
Les navigateurs Web modernes prennent en charge une fonctionnalité appelée politique de sécurité du contenu (CSP), qui limite le contenu utilisable sur une page Web. Ce défi comporte une CSP qui nous empêche d’utiliser directement les techniques d’injection CSS décrites précédemment.
Nous pouvons consulter la CSP à l’aide des outils de développement de notre navigateur, en examinant les en-têtes de la page :

Remarquez que la directive img-src de la politique n’autorise que « self » et « data ». Cela signifie que nous ne pouvons utiliser que des URL appartenant au même domaine que la page de messages éphémères. En particulier, nous ne pouvons pas utiliser un domaine qui nous appartient pour vérifier quelles URL ont été consultées par injection CSS. Nous pouvons toutefois contourner cette restriction en tirant parti des messages éphémères du défi.
Nous savons que les messages éphémères ne peuvent être consultés qu’une seule fois. Si un message a déjà été consulté, il renvoie alors un code d’état 404. Nous pouvons donc utiliser des URL de messages éphémères dans nos sélecteurs d’injection CSS et vérifier leur code d’état pour savoir si elles ont été consultées.
Résumé de l’investigation
Voici les informations clés recueillies lors de cette investigation, qui nous aideront à élaborer notre exploit. Si vous n’avez pas entièrement compris la section précédente (ou si vous l’avez simplement parcourue), ces points suffiront pour aborder la suite :
La page de consultation du message est vulnérable à l’injection CSS via le paramètre de chaîne de requête
?color=. Cela signifie que nous pouvons définir des styles arbitraires sur la page.La politique de sécurité du contenu n’autorise que les URL du même domaine. Cela signifie que notre navigateur bloquera toutes les URL externes.
Un message ne peut être consulté qu’une seule fois. Cela signifie que nous obtiendrons un code d’état 200 si le message n’a jamais été consulté, et un code d’état 404 s’il l’a déjà été.
Le flag se trouve dans l’attribut
data-flagde la page de consultation du message. Cette valeur est renseignée par le paramètre du cookie « flag » du navigateur. Le bot d’administration a probablement ce cookie configuré avec le bon flag.
Voyons comment combiner tous ces éléments pour élaborer un exploit.
L’exploit
Nous pouvons utiliser l’injection CSS pour récupérer les informations du flag, caractère par caractère, à partir de l’attribut data-flag. Cependant, nous devons utiliser une URL située sur le même domaine que celui du défi. Lors de l’exfiltration de caractères par injection CSS, l’important est de savoir si une URL a été consultée afin de déterminer quels caractères sont exfiltrés.
Heureusement, nous pouvons créer des messages sur le domaine des messages éphémères, un pour chaque caractère. Chaque URL peut être associée à des sélecteurs CSS contenant un caractère donné :
Nous pouvons réaliser tout cela en Python, par exemple avec le code suivant :
La fonction generate_payload doit créer un sélecteur CSS qui déclenche l’URL associée si notre hypothèse est correcte. Voici à quoi ressemble cette fonction :
La fonction generate_message doit créer un nouveau message à l’aide du point de terminaison POST /new, appelé par le bouton Send!. Une fois le message créé, nous devons stocker les URL du bot d’administration et de consultation du message générées par cet appel. Nous pouvons extraire ces URL de la réponse à l’aide d’une expression régulière :
Une fois nos messages et les charges utiles de leurs sélecteurs CSS générés, nous devons créer un message supplémentaire que le bot d’administration consultera. Ce message recevra notre charge utile et déclenchera nos URL de message si notre hypothèse correspond au contenu de l’attribut « data-flag ».
Après avoir déclenché le bot d’administration, nous devons attendre quelques secondes pour qu’il récupère et affiche la page. Une fois l’attente terminée, nous vérifions le code d’état de toutes les URL de nos messages correspondant aux hypothèses, afin de déterminer laquelle renvoie un code 404. Si nous en trouvons une, nous savons que le bot d’administration a consulté l’URL et pouvons confirmer notre hypothèse.
Encodage de la chaîne de requête
Vous avez peut-être remarqué, en lisant le code ci-dessus, que nous utilisons quote_plus pour encoder la chaîne de requête entière, y compris le nom du paramètre et le délimiteur de chaîne de requête ?color=. Cela s’explique par le fait que le bot d’administration ignore les charges utiles transmises dans la chaîne de requête.
Pour contourner ce problème, nous pouvons faire croire au bot d’administration que la chaîne de requête fait partie du chemin de l’URL et, ainsi, l’inclure dans l’URL de consultation du message qu’il visite.
Par exemple, si nous avons le chemin d’URL suivant : /admin-bot/f785781-55f4-4eca-8565-8511f12a4ffc**?color=**ffffff%7D…
Le bot d’administration ignorera le paramètre de chaîne de requête et consultera simplement le chemin d’URL /view/f785781-55f4-4eca-8565-8511f12a4ffc
Toutefois, si nous encodons intégralement le paramètre de chaîne de requête, il fera alors partie du chemin de l’URL :
/admin-bot/f785781-55f4-4eca-8565-8511f12a4ffc**%3Fcolor%3D**ffffff%7D…
Le serveur Web semble décoder cette valeur avant de la transmettre au bot. Cela signifie que l’URL de la page qu’il consultera sera alors :
/view/f785781-55f4-4eca-8565-8511f12a4ffc?color=ffffff}...
Récupération du flag complet
Maintenant que nous savons formuler correctement nos hypothèses, nous devons répéter le processus jusqu’à ce que notre hypothèse corresponde entièrement au flag. Dès que nous atteignons un }, nous savons que nous pouvons nous arrêter.
Nous savons également, d’après les valeurs de flag d’autres défis, que les flags Snyk commencent par 'SNYK{' et que le contenu entre accolades est un UUID. C’est utile, car nous pouvons limiter notre alphabet aux caractères « a-f » et « 0-9 ». Cela réduira considérablement le temps nécessaire pour tester nos hypothèses à chaque étape.
Code de l’exploit
En réunissant tous ces éléments, voici à quoi ressemble le code source final :
En exécutant ce code, nous avons la satisfaction de voir le flag SNYK exfiltré caractère par caractère :

Message éphémère, défi relevé
Nous avons découvert que le défi Disposable Message était vulnérable à l’injection CSS. Malgré la difficulté causée par une politique de sécurité du contenu restrictive, nous avons pu concevoir un exploit pour exfiltrer le flag. Pour cela, nous avons tiré parti du code d’état 404 renvoyé par les messages déjà consultés.
J’espère que vous avez apprécié ce compte rendu du CTF et peut-être même appris quelque chose de nouveau ! Les défis CTF sont un excellent moyen de découvrir des exploits utilisés dans le monde réel et, par conséquent, de mieux vous en défendre dans vos propres systèmes. 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é.



