Эксплойт через комментарий: как участник Uiuctf 2025 добился выполнения кода в python

Участник соревнования UIUCTF 2025 поделился подробным анализом, как ему удалось добиться исполнения произвольного кода на сервере, несмотря на крайне ограниченные условия — он мог управлять лишь содержимым текстового комментария в Python-скрипте.

Суть задания заключалась в следующем: участники отправляли HTTP-запрос на сервер, запускавший Python-скрипт, который создавал новый файл с случайным именем и расширением `.py`. Внутри этого нового скрипта в качестве комментария вставлялись пользовательские данные, при этом из строки предварительно удалялись символы перевода строки `n` и возврата каретки `r`. После этого созданный файл выполнялся с помощью команды `python3 имя_файла.py`.

Задача состояла в том, чтобы, имея доступ лишь к полю комментария, извлечь содержимое файла `/home/ctfuser/flag`. Код, формировавший и запускавший скрипт, выглядел примерно следующим образом:

```python
with open(f"{filename}.py", "w") as f:
f.write(f"# {comment}nprint('Thanks for playing!')n")
os.system(f"python3 {filename}.py")
```

Иными словами, пользовательский ввод вставлялся только в область комментария, а затем файл исполнялся. Задание было вдохновлено известной уязвимостью в парсере CPython, при которой нулевой байт (``) интерпретировался как конец строки, позволяя скрыть вредоносный код внутри комментариев. Эта уязвимость была устранена в версиях CPython 3.11.4 и 3.12.0, однако до этого она могла использоваться для обхода фильтрации.

В рамках конкурса фильтрация ограничивалась удалением символов `n` и `r`, но использование уязвимого интерпретатора позволило бы обойти это ограничение с помощью символа ``. Однако организаторы использовали уже обновлённую версию Python, в которой проблема была исправлена, надеясь, что участники смогут найти другие недостатки в обработке синтаксиса.

Тем не менее, победивший участник не стал искать новые уязвимости в парсере. Вместо этого он использовал нетривиальную особенность Python: возможность исполнять ZIP-архивы в качестве исполняемых файлов. Эта функциональность появилась ещё в Python 2.6 и используется, например, для запуска упакованных Python-пакетов. При этом критически важно, что Python определяет ZIP-архив не по расширению файла, а по сигнатурам в его содержимом. Таким образом, файл с расширением `.py`, содержащий структуру ZIP-архива, будет интерпретирован как архив, если его содержимое соответствует формату ZIP.

Ключевой элемент ZIP — это секция EOCD (End of Central Directory), которая располагается в самом конце файла и может содержать произвольный комментарий. Если в архиве присутствует файл `__main__.py`, то при запуске команды `python архив.py` будет автоматически исполнен именно этот файл.

Участник использовал эту особенность, чтобы внедрить минимальный ZIP-архив в текст комментария. Основной сложностью было то, что конец файла `имя.py` всегда содержал строку `print("Thanks for playing!")`, которую нельзя было удалить или изменить. Однако спецификация формата ZIP позволяет располагать комментарий в EOCD после основной части архива, благодаря чему удалось корректно сформировать архивную структуру, несмотря на принудительное добавление кода после пользовательского ввода.

Таким образом, решение сводилось к созданию валидного ZIP-архива, встроенного в текст комментария, с включённым внутрь `__main__.py`, содержащим нужный код для чтения и вывода содержимого файла `/home/ctfuser/flag`. Это позволило обойти ограничения и добиться исполнения произвольного кода в условиях, где можно было управлять только текстом комментария.

Прокрутить вверх