Дата публикации:Sun, 03 Aug 2025 07:50:07 +0300
Один из участников соревнования UIUCTF 2025, подробно разобрал, как ему удалось выполнить задание, требующее добиться исполнения своего кода на сервере, имея лишь возможность изменения содержимого текста комментария в коде.
Участники могли отправить сетевой запрос к Python-скрипту, который создавал новый Python-скрипт cо случайными именем, добавлял поступившие от пользователя данные в текст комментария, вырезав символы "\n" и "\r", и запускал этот скрипт командой "python3 имя.py". Контролируя только содержимое комментария участник должен был извлечь строку из файла "/home/ctfuser/flag". Скрипт создавался следующим кодом:
comment = input("> ").replace("\n", "").replace("\r", "") code = f"""print("hello world!") # This is a comment. Here's another: # {comment} print("Thanks for playing!")""" Вместо "{comment}" подставлялись данные, поступившие от участника, и в итоге запускался следующий код:
print("hello world!") # This is a comment. Here's another: # Данные, поступившие от участника соревнования print("Thanks for playing!") Задание было сформировано по мотивам уязвимости в парсере CPython, который обрабатывал символ с нулевым кодом, как окончание строки (уязвимость, например, можно было использовать для скрытия вредоносных действий в тексте комментария). Проблема была устранена в выпусках CPython 3.12.0 и 3.11.4. В применяемом в конкурсе обработчике вырезались только символы "\n" и "\r", но при использовании уязвимой версии СPython участник мог использовать символ "\0" как разделитель. Тем не менее этот трюк не сработал так как в конкурсе использовалась уже исправленная версия CPython c расчётом, что в парсере могут оставаться ещё какие-то похожие ошибки и участники смогут их выявить.
Успешно справившийся с заданием участник не стал искать новые уязвимости в парсере, которые бы позволили разбить строку на чаcти, а воспользовался особенностью выполнения в Python файлов по типу их содержимого. Например, вместо исходного кода в файл с расширением ".py" можно поместить прокэшированный байткод, сохраняемый в файлах с расширением ".pyc", и подобный файл будет выполнен. В рассматриваемом конкурсе участник мог контролировать только содержимое в середине файла, поэтому не мог добавить свой заголовок для искажения MIME-типа.
Задачу удалось решить, воспользовавшись тем, что Python начиная с ветки 2.6 может исполнять содержимое ZIP-архивов для поставки Python-пакетов в сжатом виде. Как и в случае с кэшем байткода наличие zip-архива определяется по содержимому, а не по расширению файла, т.е. в "файл.py" можно поместить zip-архив и при запуске командой "python файл.py" он будет обработан, как сжатый Python-пакет. При этом ZIP-архивы в Python индексируются не по заголовку в начале файла, а по секции EOCD (End of Central Directory Record) в конце файла. При наличии в архиве файла "__main__.py", этот файл запускается автоматически при прямом запуске архива командой "python архив".
Конкурсная задача была решена генерацией подобного ZIP-архива и подстановкой его в текст комментария. Для сохранения корректности структуры файла в условиях наличия в конце исходного файла вызова 'print("Thanks for playing!")', было использовано наличие в EOCD-секции области комментария, размещаемой в самом конце.

Ссылка на оригинал: https://www.opennet.ru/opennews/art.shtml?num=63669