(RCE) Flask + Werkzeug генерируем куку на основе PIN кода
Предисловие и карма
Впервые, с задачей генерации PIN кода я столкнулся во время прохождения лаборатории у Offensive, за день перед экзаменом OSCP. Мне было жутко лень решать машину Reconstruction, так как за спиной было много проделанной работы и я был почти уверен, что сдам экзамен, но на душе оставалось ощущение, что с данной задачей я еще встречусь. Так и произошло. В ходе противостояния The Standoff было не менее 5 машин, точкой входа для которых как раз была генерация PIN и удаленное выполнение Python кода через консоль Debug мода.
В процессе противостояния The Standoff данный вектор пыталось эксплуатировать множество команд, что привело к постоянной блокировке панели ввода PIN кода. Основная задача была – найти способ обойти ограничения на количество неудачных попыток ввода PIN кода, что удалось сделать моему коллеге.
Генерация PIN кода
Flask — это Framework для создания веб-приложений на языке программирования Python, использующий набор инструментов Werkzeug.
Когда веб-сервер запущен с включенным Debug mode, в случае ошибки справа располагается иконка консоли (или необходимо перейти в веб-директорию console), где можно выполнять Python код, однако для этого необходимо предоставить PIN, генерируемый при старте веб-сервера.
Код генерации PIN находится в файле __init__.py, который может располагаться в следующих директориях:
/usr/local/lib/python*/site-packages/werkzeug/debug/__init__.py
/usr/local/lib/python*/dist-packages/werkzeug/debug/__init__.py
~/.local/lib/python*/site-packages/werkzeug/debug/__init__.py
~/.local/lib/python*/dist-packages/werkzeug/debug/__init__.py
За генерацию PIN отвечает метод get_pin_and_cookie_name(). Сам код генерируется на основе данных из массивов probably_public_bits и private_bits. При наличии уязвимости, позволяющей читать локальные файлы системы, можно собрать необходимые данные и сгенерировать собственный PIN код.
Необходимые данные для генерации PIN кода:
probably_public_bits[0] – пользователь, который запустил веб-сервер (анализируем /etc/passwd)
probably_public_bits[1] – по умолчанию flask.app
probably_public_bits[2] – по умолчанию Flask
probably_public_bits[3] – абсолютный путь к файлу app.py во flask директории, данное значение можем найти из ошибки в Debug mode (/usr/local/lib/python3.6/dist-packages/flask/app.py)
private_bits[0] – MAC адрес сетевого интерфейса (/sys/class/net/<INTERFACE>/address), который необходимо перевести в десятичную систему счисления.
private_bits[1] – machine id, можно получить из файла /etc/machine-id, также необходимо прочитать файл /proc/self/cgroup, если в первой cтроке после крайнего слеша будет некоторое значение, то его необходимо добавить к machine id, иначе используем только machine id.
Для генерации собственного PIN кода можно использовать следующий скрипт:
Однако, генерация PIN может немного различаться под разные версии Python. В моем случае основное отличие заключалось в различных алгоритмах хеширования. Рекомендую сравнить код метода get_pin_and_cookie_name() из файла __init__.py уязвимой машины с вашим кодом для генерации PIN, если это возможно.
Сгенерировав собственный PIN, вводим его и получаем доступ к консоли, где производим удаленное выполнение Python кода.
Генерация Cookie и обход ограничений
При превышении количества неудачных попыток ввода PIN, панель блокируется и спасти в данной ситуации может только рестарт веб-сервера.
Однако, если просмотреть запрос на выполнение Python кода, можно заметить некоторую куку, которая была присвоена после ввода легитимного PIN.
Данная кука является уникальной и состоит из трех частей:
- имя: __wzd4db50c13a80774d469cb, которое генерируется на стадии генерации PIN в методе get_pin_and_cookie_name();
- время: 1623743328, количество секунд с 1 января 1970 00:00:00 UTC минус високосные секунды;
- посоленный хешированный легитимный PIN: за генерацию данного значения отвечает функция hash_pin(), по умолчанию соль прописана в теле функции и равна “shittysalt”;
Получается, для генерации собственной куки, которая позволит сразу перейти к этапу выполнения Python кода даже если панель ввода PIN заблокирована, необходимо сгенерировать легитимный PIN и вызвать функцию hash_pin() для генерации 3-тей части куки, которая не известна.
Далее перехватываем запрос на ввод любого PIN кода, заменяем GET параметр pin на frm=0 и добавляем сгенерированную нами куку.
В результате получаем удаленное выполнение Python кода обойдя заблокированную панель ввода PIN.
Ссылка на скрипт для генерации PIN кода и куки:
- 🦋 Слитая информация - @Leakinfo
- 🎭 Наша группа > - Точка входа
- ❤️ Поблагодарить Bitcoin