Ein Teilnehmer des Wettbewerbs UIUCTF 2025 erklärte im Detail, wie es ihm gelang, eine Aufgabe zu erfüllen, die erforderte, dass sein Code auf dem Server ausgeführt wird, indem er lediglich die Möglichkeit hatte, den Inhalt des Kommentartextes im Code zu ändern.
Die Teilnehmer konnten eine Netzwerkanfrage an ein Python-Skript senden, das ein neues Python-Skript mit einem zufälligen Namen erstellte, die vom Benutzer übergebenen Daten in den Kommentartext einfügte, die Zeichen «\n» und «\r» entfernte und dieses Skript mit dem Befehl «python3 name.py» ausführte. Indem der Teilnehmer nur den Inhalt des Kommentars kontrollierte, sollte er eine Zeichenkette aus der Datei «/home/ctfuser/flag» extrahieren. Das Skript wurde durch den folgenden Code erstellt: comment = input(«> «).replace(«\n», «»).replace(«\r», «») code = f»»»print(«hello world!») # Dies ist ein Kommentar. Hier ist ein weiterer: # {comment} print(«Thanks for playing!»)»»»
Statt «{comment}» wurden die vom Teilnehmer übermittelten Daten eingesetzt, und dadurch wurde folgender Code ausgeführt: print(«hello world!») # Dies ist ein Kommentar. Hier ist ein weiterer: # Daten, die von einem Wettbewerbsteilnehmer übermittelt wurden print(«Thanks for playing!»)
Die Aufgabe war inspiriert von einer Schwachstelle im CPython-Parser, der das Null-Zeichen als Ende der Zeile behandelte (diese Schwachstelle konnte beispielsweise dazu verwendet werden, böswillige Aktivitäten im Kommentartext zu verbergen). Das Problem wurde in den Versionen CPython 3.12.0 und 3.11.4 behoben. Im im Wettbewerb verwendeten Handler wurden nur die Zeichen «\n» und «\r» ausgeschnitten, aber durch die Verwendung einer verwundbaren Version von CPython konnte der Teilnehmer das Zeichen «\0» als Trennzeichen verwenden. Dennoch funktionierte dieser Trick nicht, da im Wettbewerb bereits eine korrigierte Version von CPython verwendet wurde, mit der Annahme, dass es im Parser möglicherweise noch andere ähnliche Fehler gibt, die die Teilnehmer entdecken könnten.
Der Teilnehmer, der die Aufgabe erfolgreich bewältigte, suchte nicht nach neuen Schwachstellen im Parser, die es ihm ermöglicht hätten, die Zeichenkette in Teile zu zerlegen, sondern nutzte die Besonderheit von Python, dass Dateien je nach Inhalt ausgeführt werden. Beispielsweise kann anstelle des Quellcodes in eine Datei mit der Erweiterung «.py» der zwischengespeicherte Bytecode eingefügt werden, der in Dateien mit der Erweiterung «.pyc» gespeichert wird, und eine solche Datei wird ausgeführt. Im betrachteten Wettbewerb konnte der Teilnehmer nur den Inhalt in der Mitte der Datei steuern, weshalb er keine eigenen Header hinzufügen konnte, um den MIME-Typ zu verzerren.
Die Aufgabe konnte gelöst werden, indem ich die Tatsache nutzte, dass Python seit der Version 2.6 den Inhalt von ZIP-Archiven ausführen kann, um Python-Pakete in komprimierter Form bereitzustellen. Wie im Fall des Bytecode-Cache wird das Vorhandensein eines ZIP-Archivs durch den Inhalt und nicht durch die Dateiendung bestimmt, d.h. in "datei.py" kann ein ZIP-Archiv untergebracht werden und beim Ausführen des Befehls "python datei.py" wird es als komprimiertes Python-Paket verarbeitet. Dabei werden ZIP-Archive in Python nicht nach dem Header am Anfang der Datei, sondern nach dem EOCD-Bereich (End of Central Directory Record) am Ende der Datei indiziert. Wenn im Archiv die Datei "__main__.py" vorhanden ist, wird diese Datei automatisch beim direkten Ausführen des Archivs durch den Befehl "python archiv" gestartet.
Die Wettbewerbsaufgabe wurde gelöst, indem ein ähnliches ZIP-Archiv generiert und in den Kommentartext eingefügt wurde. Um die Struktur der Datei zu erhalten, obwohl am Ende der ursprünglichen Datei der Aufruf ‘print(„Danke fürs Spielen!“)’ steht, wurde der Kommentarbereich in der EOCD-Sektion verwendet, der ganz am Ende platziert ist.

Quelle: opennet.ru
