June 17, 2021

(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, генерируемый при старте веб-сервера.

1.png

Код генерации 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 код.

2.png

Необходимые данные для генерации 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)​

3.png

private_bits[0] – MAC адрес сетевого интерфейса (/sys/class/net/<INTERFACE>/address), который необходимо перевести в десятичную систему счисления.

4.png

private_bits[1] – machine id, можно получить из файла /etc/machine-id, также необходимо прочитать файл /proc/self/cgroup, если в первой cтроке после крайнего слеша будет некоторое значение, то его необходимо добавить к machine id, иначе используем только machine id.

5.png

Для генерации собственного PIN кода можно использовать следующий скрипт:

https://gist.githubusercontent.com/InfoSecJack/70033ecb7dde4195661a1f6ed7990d42/raw/028384ef695e376d412f9276ad27b2c916d4f748/get_flask_pin.py

Однако, генерация PIN может немного различаться под разные версии Python. В моем случае основное отличие заключалось в различных алгоритмах хеширования. Рекомендую сравнить код метода get_pin_and_cookie_name() из файла __init__.py уязвимой машины с вашим кодом для генерации PIN, если это возможно.

6.png

Сгенерировав собственный PIN, вводим его и получаем доступ к консоли, где производим удаленное выполнение Python кода.

7.png

Генерация Cookie и обход ограничений

При превышении количества неудачных попыток ввода PIN, панель блокируется и спасти в данной ситуации может только рестарт веб-сервера.

8.jpg

Однако, если просмотреть запрос на выполнение Python кода, можно заметить некоторую куку, которая была присвоена после ввода легитимного PIN.

9.png

Данная кука является уникальной и состоит из трех частей:​

  • имя: __wzd4db50c13a80774d469cb, которое генерируется на стадии генерации PIN в методе get_pin_and_cookie_name();​
10.png
  • время: 1623743328, количество секунд с 1 января 1970 00:00:00 UTC минус високосные секунды;​
11.png
  • посоленный хешированный легитимный PIN: за генерацию данного значения отвечает функция hash_pin(), по умолчанию соль прописана в теле функции и равна “shittysalt”;​
12.png

Получается, для генерации собственной куки, которая позволит сразу перейти к этапу выполнения Python кода даже если панель ввода PIN заблокирована, необходимо сгенерировать легитимный PIN и вызвать функцию hash_pin() для генерации 3-тей части куки, которая не известна.

13.png
14.png

Далее перехватываем запрос на ввод любого PIN кода, заменяем GET параметр pin на frm=0 и добавляем сгенерированную нами куку.

15.png

В результате получаем удаленное выполнение Python кода обойдя заблокированную панель ввода PIN.

16.png

Ссылка на скрипт для генерации PIN кода и куки:

https://github.com/WiIs0n/Flask-cookie-generation-based-on-PIN-code/blob/main/get_flask_pin_and_cookie.py

Источник