Это были киберучения (Red Teaming) для зрелой компании. Всё началось с обнаруженной SSRF уязвимости в аналитическом сервисе. Через него удалось отправлять HTTP-запросы к внутренним сервисам. Дальнейшая разведка показала, что внутри сети отсутствовала нормальная сегментация, а ClickHouse, Hadoop и несколько других внутренних сервисов не требовали аутентификации. Прямого RCE через ClickHouse получить не удалось, зато он стал промежуточным звеном, позволившим использовать его для отправки POST-запросов к Hadoop YARN, несмотря на то что исходная SSRF уязвимость поддерживала только GET-запросы.

Обнаружение уязвимости на периметре

На внешнем периметре был обнаружен аналитический сервис, защищённый авторизацией. Помимо основной функциональности, на нём оказался доступен debug-эндпоинт, который возвращал информацию об окружении, версии контейнера и хосте, на котором работал сервис. Для разработчиков такие данные бывают полезны, но выкладывать их в публичный доступ не лучшая идея.

В ходе дальнейшего исследования кода фронтенда был обнаружен интересный обработчик:

При этом, валидация параметра url отсутствовала. Сервис просто отправлял GET-запросы по адресу, указанному в url, что является классической SSRF уязвимостью.

Например, был выполнен такой запрос:

Как эксплуатировали SSRF уязвимость

После подтверждения уязвимости можно было переходить к разведке внутренней сети. Некоторые публичные поддомены компании имели внутренние IP-адреса в A-записях, так что мы быстро смогли убедиться, что имеем широкий доступ во внутреннюю продуктовую инфраструктуру. Мы принялись за разведку, сканируя различные внутренние маршруты на популярные порты, опираясь на уже известные нам IP-адреса и постепенно расширяя список известных нам маршрутов (например, нашли приложение на хосте 10.1.2.3, а в логах самого приложения обнаружили IP-адрес другого хоста с адресом 10.3.2.1).

Но возможности SSRF оказались ограничены. Можно было отправлять только HTTP GET-запросы с фиксированным набором заголовков (под капотом — asyncio). От многих идей по дальнейшей эксплуатации пришлось отказаться, однако этих возможностей оказалось достаточно, чтобы проанализировать ответы и понять, какие сервисы доступны внутри сети.

Что оказалось доступно

Поскольку мы изначально не понимали, насколько интенсивно мы можем позволить себе сканировать сеть через SSRF, мы эмпирически выбирали наиболее интересные web-порты: 80, 8080, 8181, 9090, 8123, 8090 и т. д. На таких портах бывают различные dev-инструменты, СУБД и прочее.

Через SSRF удалось обнаружить несколько внутренних сервисов, среди которых особый интерес представляли ClickHouse и Hadoop YARN.

YARN сразу выглядел наиболее привлекательной целью. REST API позволяет создавать приложения, а описание приложения содержит команды, которые будут выполняться операционной системой. Проблема заключалась в том, что создание приложения требует POST-запроса, а наш SSRF работал только с GET.

На первый взгляд цепочка на этом заканчивалась.

Почему ClickHouse сам по себе не привёл к RCE

Мы нашли несколько интерфейсов ClickHouse, доступных без аутентификации. Таким образом, хост из внутренней сети, которому был доступен HTTP-интерфейс ClickHouse, мог выполнять SQL-запросы. Это уже являлось частичным выполнением одной из целей проекта, поскольку нам удалось найти пользовательские данные на серверах аналитики.

Первой мыслью было добиться выполнения команд средствами самого ClickHouse. Сервер по умолчанию умеет выполнять системные команды:

Но для выполнения данной команды необходима нестандартная настройка <user_scripts_path>.

В проде такая конфигурация встречается редко и этот случай не стал исключением.

Еще одним интересным вариантом является исполнение кода в PostgreSQL через оператор postgresql в ClickHousehttps://clickhouse.com/docs/sql-reference/table-functions/postgresql#implementation-details.

Эти и другие векторы не привели к успеху, но наше внимание привлекла табличная функция url() — она умеет выполнять HTTP-запросы, а при использовании INSERT INTO FUNCTION url() запрос отправляется методом POST, в документации описывается возможность задания произвольных заголовков и параметров.

Это позволило бы обойти ограничение и добиться полноценной эксплуатации SSRF через Hadoop Yarn.

Особенности функции url()

Из возможных форматов нам подходил RawBLOB, поскольку требовалась отправка json. Другие отпали по разным причинам — какие-то, например, используют ndjson в качестве Content-Type — передают json построчно.

Для формата RawBLOB выяснилось несколько нюансов:

Во-первых, запросы всегда отправляются с заголовком:

Transfer-Encoding: chunked

Hadoop корректно работал с данным заголовком.

Во-вторых, параметр

headers('Content-Type'='application/json')

в старых версиях ClickHouse не заменяет стандартный Content-Type, а лишь добавляет ещё один заголовок. Поведение удалось воспроизвести в контейнере с ClickHouse 24.10.

Для примера был выполнен такой запрос:

Hadoop использовал первый встретившийся Content-Type, что не позволяло выполнить произвольный код.

Начиная с версии 24.11 работа с заголовками изменилась. Именно эта версия оказалась подходящей для взаимодействия с YARN. Начиная с этой версии, заголовок заменялся.

Таким образом, определение версии подходящего релиза ClickHouse стало ключевым фактором во всей цепочке.

Социальная инженерия

Во время поиска подходящего ClickHouse выяснилось, что далеко не все найденные экземпляры работали на нужной версии ≥ 24.11. Сканирование внутренних подсетей не дало успеха.

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

Именно так был обнаружен нужный экземпляр ClickHouse.

Цепочка эксплуатации

Сначала через SSRF был получен идентификатор кластера:

Из ответа извлекался clusterId, необходимый для формирования application-id.

Запрос на создание приложения в YARN через url():

Неочевидная проблема с пользователем

После отправки первого запроса приложение создавалось, но почти сразу завершалось ошибкой:

После изучения документации и логов было выявлено, что ошибка связана не с самим запросом, а с пользователем, от имени которого YARN принимает команду.

Если имя пользователя не передаётся явно, Hadoop использует пользователя dr.who. Далее кластер пытается определить его группы, и именно на этом этапе выполнение завершалось ошибкой, так как такого пользователя не было на самом сервере.

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

Так как данный пользователь был в системе (создаётся при установке Hadoop) — приложение успешно запустилось.

Финальный запрос

В результате вся цепочка свелась к следующему запросу:

Выводы

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

Не менее важна и сегментация сети. Хост, находящийся в DMZ, не должен иметь прямой доступ к базам данных, системам оркестрации и другим критичным компонентам. Если такие соединения разрешены, SSRF становится лишь первой ступенью атаки.

И ещё один практический вывод. Поведение программного обеспечения заметно меняется от версии к версии. Разница между ClickHouse 24.10 и 24.11 в данном случае оказалась критичной. Поэтому любые техники эксплуатации стоит проверять на конкретной сборке, а не полагаться исключительно на документацию или чужие исследования.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *