Uno de los participantes de la competencia UIUCTF 2025 explicó en detalle cómo pudo completar la tarea, que requería ejecutar su código en el servidor, teniendo solo la posibilidad de modificar el contenido del texto del comentario en el código.
Los participantes podían enviar una solicitud de red a un script de Python que creaba un nuevo script de Python con un nombre aleatorio, añadía los datos proporcionados por el usuario al texto del comentario, eliminando los caracteres «\n» y «\r», y ejecutaba este script con el comando «python3 nombre.py». Controlando solo el contenido del comentario, el participante debía extraer una línea del archivo «/home/ctfuser/flag». El script se creaba con el siguiente código: comment = input(«> «).replace(«\n», «»).replace(«\r», «») code = f»»»print(«hello world!») # Este es un comentario. Aquí hay otro: # {comment} print(«Gracias por participar!»)»»»
En lugar de «{comment}» se insertaban los datos proporcionados por el participante, y al final se ejecutaba el siguiente código: print(«hello world!») # Este es un comentario. Aquí hay otro: # Datos proporcionados por el participante de la competencia print(«Gracias por participar!»)
La tarea se formuló en base a una vulnerabilidad en el parser de CPython, que trataba el carácter con código cero como un final de línea (la vulnerabilidad, por ejemplo, se podía utilizar para ocultar acciones maliciosas en el texto del comentario). El problema se solucionó en las versiones CPython 3.12.0 y 3.11.4. En el manejador utilizado en la competencia, solo se eliminaban los caracteres «\n» y «\r», pero usando una versión vulnerable de CPython, el participante podía usar el carácter «\0» como delimitador. Sin embargo, este truco no funcionó ya que en la competencia se utilizaba una versión corregida de CPython, con la expectativa de que pudieran existir aún algunos errores similares en el parser y los participantes pudieran identificarlos.
El participante que logró completar la tarea no buscó nuevas vulnerabilidades en el parser que permitieran dividir la cadena en partes, sino que utilizó una característica de ejecución en Python de archivos según su contenido. Por ejemplo, en lugar del código fuente, se puede colocar un bytecode en caché en un archivo con la extensión «.py», y dicho archivo se ejecutará. En la competencia discutida, el participante solo podía controlar el contenido en medio del archivo, por lo que no podía añadir su encabezado para distorsionar el tipo MIME.
Se logró resolver la tarea aprovechando que Python, a partir de la versión 2.6, puede ejecutar el contenido de archivos ZIP para la entrega de paquetes Python en formato comprimido. Al igual que en el caso de la caché de bytecode, la presencia de un archivo ZIP se determina por su contenido y no por su extensión; es decir, se puede colocar un archivo ZIP en «archivo.py» y al ejecutarlo con el comando «python archivo.py», será procesado como un paquete Python comprimido. Además, los archivos ZIP en Python se indexan no por el encabezado al inicio del archivo, sino por la sección EOCD (Registro de Fin de Directorio Central) al final del archivo. Si hay un archivo «__main__.py» en el archivo ZIP, este se ejecuta automáticamente al lanzar el archivo con el comando «python archivo».
La tarea del concurso se resolvió generando un archivo ZIP similar y sustituyéndolo en el texto del comentario. Para mantener la corrección de la estructura del archivo, dado que al final del archivo fuente estaba la instrucción ‘print(«¡Gracias por participar!»)’, se utilizó la existencia de un área de comentarios en la sección EOCD, ubicada al final del archivo.

Fuente: opennet.ru
