Um dos participantes da competição UIUCTF 2025 analisou detalhadamente como conseguiu executar uma tarefa que exigia a execução de seu código no servidor, tendo apenas a capacidade de alterar o conteúdo do texto do comentário no código.
Os participantes podiam enviar uma solicitação de rede para um script Python, que criava um novo script Python com um nome aleatório, adicionava os dados recebidos do usuário ao texto do comentário, removendo os caracteres "\n" e "\r", e executava esse script com o comando "python3 nome.py". Controlando apenas o conteúdo do comentário, o participante deveria extrair uma linha do arquivo "/home/ctfuser/flag". O script era criado com o seguinte código: comment = input("> ").replace("\n", "").replace("\r", "") code = f"""print("hello world!") # Este é um comentário. Aqui está outro: # {comment} print("Thanks for playing!")"""
No lugar de "{comment}", foram inseridos os dados recebidos do participante, e, ao final, o seguinte código era executado: print("hello world!") # Este é um comentário. Aqui está outro: # Dados recebidos do participante da competição print("Thanks for playing!")
A tarefa foi baseada em uma vulnerabilidade no parser CPython, que tratava o caractere com código zero como um terminador de linha (a vulnerabilidade, por exemplo, poderia ser usada para ocultar ações maliciosas no texto do comentário). O problema foi resolvido nas versões CPython 3.12.0 e 3.11.4. No manipulador utilizado na competição, apenas os caracteres "\n" e "\r" eram removidos, mas ao usar uma versão vulnerável do CPython, o participante poderia usar o caractere "\0" como delimitador. No entanto, esse truque não funcionou, pois a versão corrigida do CPython já estava em uso, levando em conta que ainda poderiam existir alguns outros erros semelhantes no parser que os participantes poderiam descobrir.
O participante que teve sucesso na tarefa não tentou encontrar novas vulnerabilidades no parser que permitissem dividir a string em partes, mas utilizou uma característica da execução de arquivos Python de acordo com seu tipo de conteúdo. Por exemplo, em vez de código fonte, um arquivo com a extensão " .py" poderia conter código de byte cacheado, armazenado em arquivos com a extensão ".pyc", e tal arquivo seria executado. Na competição em questão, o participante só poderia controlar o conteúdo no meio do arquivo, portanto, não poderia adicionar seu próprio cabeçalho para distorcer o tipo MIME.
O problema foi resolvido aproveitando o fato de que o Python, a partir da versão 2.6, pode executar o conteúdo de arquivos ZIP para entrega de pacotes Python em formato compactado. Assim como no caso do cache de bytecode, a presença de um arquivo zip é determinada pelo conteúdo e não pela extensão do arquivo, ou seja, em "arquivo.py" pode-se colocar um arquivo zip e ao executar o comando "python arquivo.py" ele será tratado como um pacote Python compactado. Nesse caso, os arquivos ZIP em Python são indexados não pelo cabeçalho no início do arquivo, mas pela seção EOCD (End of Central Directory Record) no final do arquivo. Se houver um arquivo "__main__.py" no arquivo zip, este será executado automaticamente ao se iniciar o zip com o comando "python arquivo".
A tarefa do concurso foi solucionada gerando um arquivo ZIP semelhante e inserindo-o no texto do comentário. Para preservar a integridade da estrutura do arquivo, dado que no final do arquivo original existia uma chamada 'print("Thanks for playing!")', foi utilizada a presença de um comentário na seção EOCD, localizado na parte final.

Fonte: opennet.ru
