One of the participants of the UIUCTF 2025 competition detailed how they managed to complete a task that required executing their code on the server while only having the ability to modify the content of the comment text within the code.
Participants could send a network request to a Python script that created a new Python script with a random name, added the data from the user to the comment text by trimming the characters "\n" and "\r", and executed this script with the command "python3 name.py". By controlling only the content of the comment, the participant had to extract a string from the file "/home/ctfuser/flag". The script was created with the following code: comment = input("> ").replace("\n", "").replace("\r", "") code = f"""print("hello world!") # This is a comment. Hereās another: # {comment} print("Thanks for playing!")"""
Instead of "{comment}", the data received from the participant was inserted, resulting in the execution of the following code: print("hello world!") # This is a comment. Hereās another: # Data received from the competition participant print("Thanks for playing!")
The task was inspired by a vulnerability in the CPython parser, which treated the null character as a line terminator (this vulnerability could be used to conceal malicious actions in the comment text). The problem was fixed in CPython releases 3.12.0 and 3.11.4. In the version used in the competition, only the characters "\n" and "\r" were stripped, but using a vulnerable version of CPython, the participant could use the "\0" character as a delimiter. However, this trick did not work because the competition used an already patched version of CPython with the assumption that there might still be similar errors in the parser that participants could discover.
The participant who successfully completed the task did not seek out new vulnerabilities in the parser that would allow breaking the string into parts, but instead took advantage of the way Python executes files based on their content. For example, instead of the source code, it is possible to place cached bytecode into a file with the extension ".py", and such a file will be executed. In the contest in question, the participant could only control the content in the middle of the file, thus they could not add their header to distort the MIME type.
The task was solved by taking advantage of the fact that Python, starting from version 2.6, can execute the contents of ZIP archives to deliver Python packages in compressed form. As with bytecode caching, the presence of a ZIP archive is determined by its content rather than the file extension, meaning a 'file.py' can contain a ZIP archive and when run with the command 'python file.py', it will be processed as a compressed Python package. Moreover, ZIP archives in Python are indexed not by the header at the beginning of the file, but by the EOCD (End of Central Directory Record) section at the end of the file. If the archive contains a file named '__main__.py', this file is automatically executed when the archive is directly run with the command 'python archive'.
The competition task was solved by generating a similar ZIP archive and embedding it in the comment text. To maintain the structure of the file correctly, given the presence of the line 'print("Thanks for playing!")' at the end of the source file, the existence of a comment area in the EOCD section, placed at the very end, was utilized.

Source: opennet.ru
