Калькулятор с ключами: как чтение одного файла привело к компрометации публичного облака


В статье разберем, как через LFR-уязвимость во внешнем сервисе облачной инфраструктуры удалось полностью скомпрометировать облако.
Точкой входа стал вспомогательный калькулятор расчета тарифов, в котором нашлась возможность чтения произвольных локальных файлов. Из одной этой уязвимости выросла цепочка, которая в итоге привела к доступу к панели управления облаком под учетной записью с полными правами.
Анализ сервисов внешнего периметра
В ходе внешнего пентеста инфраструктуры крупной IT-компании команда Бастиона обнаружила публичное облако cloud.company.com, которое сразу стало одной из приоритетных целей. Это большой многокомпонентный сервис: облачный API, организации и проекты, вычислительные ресурсы, балансы и биллинг.
В ходе разведки инфраструктуры облака мы выделили домены, названия которых указывали на связь с основным сервисом. Один из них, calc.company.com, оказался сервисом расчета тарифов. Отдельно отметили домен cloud-admin.company.com: судя по названию, он относился к административной части облака.
Цепочка атаки
1. Поиск эндпоинтов
Список известных путей сервиса собрали связкой утилит getallurls и waybackurls — они тянут URL из интернет-архива и сервисов индексации. Дополнительно прошлись по JS-коду в поисках актуальных эндпоинтов: самым интересным оказался /download_report, эндпоинт выгрузки отчетов. При обращении без параметров он отдавал ошибку «нужен параметр file_name». Очевидный кандидат на проверку уязвимостей класса LFR и SSRF.
2. Проверка на LFR
Уязвимость чтения локальных файлов (Local File Read, LFR) возникает, когда приложение отдает содержимое файла по имени, которое контролирует пользователь, и при этом не фильтрует путь. Строго говоря, это именно чтение, а не включение: файл возвращается в ответе, но не исполняется.
Обычно LFR эксплуатируют в связке с Path traversal — обходом каталогов с помощью последовательностей ../../../. Они позволяют выйти за пределы каталога, к которому приложение дописывает пользовательский ввод.
Здесь этого не потребовалось: сервис принимал абсолютный путь. Так бывает в случаях, когда имя файла попадает в функцию чтения без фиксированного базового каталога, и тогда /etc/passwd и подобные файлы читаются как есть; либо когда базовый путь задан, но абсолютный путь его перекрывает (сегмент, начинающийся со слэша, отбрасывает всё, что стояло до него).
В параметр file_name подставили абсолютный путь к системному файлу:
GET /download_report?file_name=/etc/shadow HTTP/1.1
Host: calc.company.comСервер вернул содержимое /etc/shadow. Запрос не требовал авторизации, а приложение позволяло читать файлы от пользователя root.

/etc/shadow в ответе сервера на запрос к /download_report?file_name=/etc/shadow .3. От чтения файлов к секретам приложения
Возможность чтения файлов открывает несколько сценариев развития атаки.
Можно выгрузить исходники приложения и поискать в них следующую уязвимость.
Можно читать секреты процесса прямо из procfs: переменные окружения и аргументы запуска в
/proc/self/environи/proc/self/cmdline.Можно искать секреты на файловой системе — в конфигах, которые могут использоваться в инфраструктуре уязвимого приложения или соседних сервисов. Как раз в нашем случае секреты, которые мы смогли переиспользовать, лежали в конфигурационном файле приложения.
Путь к конфигу нашли перебором файлов и директорий. На несуществующий путь сервис возвращал ошибку, на существующий — отдавал код 200, но без листинга директории.
GET /download_report?file_name=/src/config.yaml HTTP/1.1
Host: calc.company.comВ ответе лежали ключи партнерского API в открытом виде:
serverspace:
url: https://apipartner.company.com
key_calc: B2F04642-40E8-218E-28FB-5C8D910A7D36
key_billing: 7E38FF3A-C714-1587-71CB-9E72302FAB3E
/src/config.yaml, полученном через LFR. Секреты key_calc и key_billing хранятся в открытом виде.Два ключа: key_calc для учетной записи калькулятора и key_billing для биллинга. Оба вели к партнерскому API apipartner.company.com. Калькулятор тарифа хранил у себя ключ, которым «ходил» в биллинговый сервис облака.
4. Партнерский API
Ключи у нас были, оставалось понять, как ими авторизоваться и к каким методам обращаться.
В документации облака описан только основной API: ключ к нему передается в заголовке CLOUD-API-KEY. О партнерском API, к которому вели key_calc и key_billing, в документации не было ничего.
Мы предположили, что ключи — это сессионные идентификаторы, и передали их в стандартном заголовке Authorization: Bearer. Сработало: Authorization: Bearer {key_billing} авторизовал запрос.
Базовый адрес партнерского API был в том же конфиге (url: https://apipartner.company.com). Пути к методам взяли из документации основного облачного API: там они шли через /v1/. Документации по партнерскому API не было, поэтому мы попробовали ту же структуру, но с /v2/. Сработало.
https://apipartner.company.com/v2/accounts/v2/accounts вернул полный список пользователей облака с их ролями и метаданными.

Ответ /v2/accounts. Перечисление пользователей облака с ролями, включая привилегированные учетные записи.Среди учетных записей выбрали привилегированные. Две оказались с полными правами — все роли, которые есть в системе. Кроме них нашлось еще десять учетных записей с ролями admin, manager, help. Ключ сервиса, который считает тарифы, дал нам возможность получить все административные учетные записи в облаке.
5. Подбор пароля привилегированной учетной записи
Выбрали две учетные записи с полными правами и запустили подбор пароля по словарю. Rate-limit на форме входа был, но привязывался к IP-адресу, а не к учетной записи. Ротация IP сняла это ограничение, и мы смогли подобрать словарный пароль к одной из этих учетных записей.
6. Вход в панель управления облаком
С полученной парой логин-пароль мы авторизовались в панели:
https://cloud.company.comПосле входа система автоматически перенаправила нас в административный компонент (Admin Area):
https://cloud-admin.company.com/
Учетная запись с полными правами в панели позволяет:
получить доступ к любой учетной записи в облаке;
получить доступ к вычислительным ресурсам любой организации;
создавать промокоды на пополнение баланса;
управлять конфигурацией облака.

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

Разбираемся в причинах
Атака стала возможной из-за сочетания трех факторов. Ни один из них сам по себе не привел бы к полной компрометации облака, но вместе они сложились в полную цепочку — от чтения локальных файлов до отсутствия 2FA (двухфакторной авторизации).
Импакт LFR-уязвимости определяется не самой возможностью чтения, а тем, как ее эксплуатируют. В большинстве случаев LFR — не финальная находка, а точка входа: импакт зависит от того, что делать дальше. Системные файлы (
/etc/passwd,/etc/shadow) подтверждают сам факт чтения. Настоящая цель — артефакты приложения: конфиги, файлы окружения, исходники, логи, приватные ключи. Дальше процесс раскручивается в зависимости от стека и функциональности приложения: до SSRF, до выгрузки исходников и поиска в них следующей уязвимости, а если чтение удается довести до включения (LFI/RFI), то и до прямого выполнения кода.Второстепенный сервис не должен носить ключ с привилегиями биллинга. Калькулятор держал в конфиге ключ к биллинговому API, и этот ключ открывал
/v2/accounts, то есть перечисление всех пользователей облака вместе с их ролями. Для расчета тарифа такие права не нужны: калькулятору достаточно узкого набора методов (тарифные планы, цены), но никак не списка всех администраторов платформы. Будь у калькулятора только правильно ограниченный ключ, утечка конфига через LFR не дала бы доступа к привилегированным методам партнерского API.Привилегированная учетная запись со словарным паролем и без второго фактора. Самые ценные аккаунты в системе, учетные записи
full admin, были защищены только паролем и одним фактором. У одной из них пароль оказался словарным. После того как мы получили список привилегированных УЗ из API, между нами и полным контролем над облаком остался только перебор по словарю. Помог и небезопасно реализованный механизм rate-limit: он считал попытки по IP-адресу, а не по учетной записи, поэтому ротация адресов позволила его обойти.
Что в итоге
Путь от чтения файлов на вспомогательном сервисе до полного контроля над облаком занял пять шагов. Сработала цепочка рядовых мисконфигураций: уязвимый к LFR параметр в стороннем сервисе, секреты в конфиге, ключ с избыточными правами, rate-limit по IP вместо учетной записи и словарный пароль администратора.

PURP — Telegram-канал, где кибербезопасность раскрывается с обеих сторон баррикад
t.me/purp_sec — инсайды и инсайты из мира этичного хакинга и бизнес‑ориентированной защиты от специалистов Бастиона
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.