Несколько дней назад раскрыл метод обхода изолированных систем исполнения кода Python, основанный на использовании давно известной ошибки, появившейся в Python 2.7, обнаруженной в 2012 году и еще не исправленной в Python 3.
Упоминается, что ошибка позволяет использовать специально связанный код Python для инициации вызова в уже освобожденную память (Use-After-Free) в Python. Изначально предполагалось, что ошибка не представляет угрозы безопасности и лишь в очень редких случаях, как правило, искусственно созданных, может привести к аварийному завершению работы скрипта.
Проблемой заинтересовался исследователь безопасности под псевдонимом kn32, который сумел подготовить функциональный эксплойт, позволяющий вызывать любую системную команду без прямого доступа к методам типа os.system.
Эксплойт реализован на чистом Python и работает без импорта внешних библиотек. и без установки драйвера "code.__new__". Из хуков используется только "встроенный.__id__", что в общем-то не запрещено. С практической стороны предлагаемый код можно использовать для обхода механизмов изоляции в различных сервисах и средах (например, в обучающих средах, онлайн-шеллах, встроенных контроллерах и т. д.), допускающих выполнение кода Python, но ограничивающих доступные вызовы и не разрешать методы доступа, такие как os.system.
Предлагаемый код аналог вызова os.system, который работает, используя уязвимость в CPython. Эксплойт работает со всеми версиями Python 3 в системах x86-64 и стабилен в Ubuntu 22.04 даже с включенными режимами безопасности PIE, RELRO и CET.
Он трабаджо сводится к получению информации об адресе одной из функций из кода Python в исполняемом коде CPython.
На основе этого адреса вычисляется базовый адрес CPython в памяти и адрес функции system() в загруженном экземпляре libc. В конце запускается прямой переход к заданной системе адресации заменой указателя первого аргумента на строку "/bin/sh".
Самый простой подход к эксплуатации — создать список с длиной, равной длине освобожденного буфера, который, скорее всего, будет иметь свой буфер элементов (ob_item), размещенный в том же месте, что и освобожденный буфер.
Это будет означать, что мы получим два разных «просмотра» одного и того же фрагмента памяти. Одно представление, представление памяти, думает, что память — это просто массив байтов, в который мы можем записывать или читать произвольно. Второе представление — это созданный нами список, который считает, что память — это список указателей PyObject. Это означает, что мы можем создавать поддельные электронные письма PyObject где-то в памяти, записывать их адреса в список, записывая в memoryview, а затем получать к ним доступ, индексируя список.
В случае PoC они записывают 0 в буфер (строка 16), а затем обращаются к нему с помощью print(L[0]). L[0] получает первый PyObject*, который равен 0, а затем print пытается получить доступ к некоторым его полям, что приводит к разыменованию нулевого указателя.
Упоминается, что эта ошибка присутствует во всех версиях python, по крайней мере, с python 2.7 и хотя эксплойт был разработан для работы практически на любой версии Python 3, это не означает, что он не воспроизводим в Python 2 (по мнению автора).
Целью эксплойта является вызов system("/bin/sh") действия которого следующие:
- Указатель бинарной функции утечки CPython
- Рассчитать базовый адрес CPython
- Рассчитайте адрес системы или вашей заглушки PLT
- Перейти к этому адресу с первым аргументом, указывающим на /bin/sh
- Победах
Наконец, упоминается, что эксплойт бесполезен в большинстве конфигураций. Однако это может быть полезно для интерпретаторов Python, пытающихся изолировать код, ограничивая импорт или используя хуки аудита.
В конце концов если вам интересно узнать об этом больше о примечании вы можете ознакомиться с оригинальной публикацией в по следующей ссылке.