L'un des participants au concours UIUCTF 2025 a analysé en détail comment il a réussi à accomplir la tâche, qui nécessitait de réaliser l'exécution de son code sur le serveur, n'ayant que la possibilité de modifier le contenu du texte de commentaire dans le code.
Les participants pouvaient envoyer une requête réseau à un script Python qui créerait un nouveau script Python avec un nom aléatoire, ajouterait la saisie de l'utilisateur au texte du commentaire, en supprimant les caractères « \n » et « \r », et exécuterait le script avec la commande « python3 name.py ». Ne contrôlant que le contenu du commentaire, le participant devait extraire une chaîne du fichier « /home/ctfuser/flag ». Le script était créé avec le code suivant : comment = input("> ").replace("\n", "").replace("\r", "") code = f"""print("hello world!") # Ceci est un commentaire. En voici un autre : # {comment} print("Merci d'avoir joué !")"""
Au lieu de « {comment} », les données reçues du participant ont été remplacées, ce qui a donné lieu à l'exécution du code suivant : print("hello world!") # Ceci est un commentaire. En voici un autre : # Données reçues du participant au concours print("Merci d'avoir joué !")
La tâche a été créée à partir d'une vulnérabilité de l'analyseur CPython, qui considérait le caractère zéro-code comme une fin de ligne (cette vulnérabilité pouvait, par exemple, servir à masquer des actions malveillantes dans le texte des commentaires). Le problème a été corrigé dans CPython 3.12.0 et 3.11.4. Dans l'analyseur utilisé lors du concours, seuls les caractères « \n » et « \r » étaient supprimés, mais avec une version vulnérable de CPython, le participant pouvait utiliser le caractère « \0 » comme séparateur. Cependant, cette astuce n'a pas fonctionné, car le concours utilisait une version déjà corrigée de CPython, en espérant que des erreurs similaires pourraient subsister dans l'analyseur et que les participants seraient en mesure de les détecter.
Le participant ayant réussi la tâche n'a pas recherché de nouvelles vulnérabilités dans l'analyseur permettant de diviser la chaîne en plusieurs parties, mais a exploité la fonctionnalité d'exécution de fichiers Python en fonction de leur type de contenu. Par exemple, au lieu du code source, il est possible de placer le bytecode mis en cache, enregistré dans des fichiers portant l'extension « .pyc », dans un fichier portant l'extension « .py », et ce fichier sera exécuté. Dans le cadre de la compétition en question, le participant ne pouvait contrôler que le contenu au milieu du fichier ; il ne pouvait donc pas ajouter son propre en-tête pour altérer le type MIME.
Le problème a été résolu grâce à la capacité de Python, à partir de la branche 2.6, à exécuter le contenu des archives ZIP pour fournir des packages Python compressés. Comme pour le cache de bytecode, la présence d'une archive ZIP est déterminée par son contenu, et non par son extension. Autrement dit, vous pouvez placer une archive ZIP dans « file.py » et, lorsqu'elle est lancée avec la commande « python file.py », elle sera traitée comme un package Python compressé. Dans ce cas, les archives ZIP en Python sont indexées non pas par l'en-tête au début du fichier, mais par la section EOCD (End of Central Directory Record) à la fin du fichier. Si l'archive contient le fichier « __main__.py », ce fichier est lancé automatiquement lorsque l'archive est lancée directement avec la commande « python archive ».
La tâche du concours a été résolue en générant une archive ZIP similaire et en la remplaçant dans le texte du commentaire. Afin de préserver l'exactitude de la structure du fichier en présence de l'appel « print(« Merci d'avoir joué ! ») » à la fin du fichier source, une zone de commentaire a été ajoutée à la fin de la section EOCD.

Source: opennet.ru
