Перейти к содержанию
Innovialabs

SSRF-атаки: как работает подделка серверных запросов

Продвинутое4 мин

Коротко

SSRF — уязвимость, при которой атакующий заставляет ваш сервер сделать запрос по выбранному им адресу, например во внутреннюю сеть или к metadata облака. Защита строится слоями: allowlist адресов, проверка IP после DNS, изоляция исходящих запросов и IMDSv2.

Что такое SSRF

SSRF (Server-Side Request Forgery) — уязвимость, при которой приложение по указанию пользователя делает HTTP-запрос со своего сервера. Атакующий подставляет адрес, до которого сам дотянуться не может, а сервер — может: localhost, внутреннюю сеть, облачный metadata-сервис.

Сервер находится внутри периметра, поэтому запрос от него проходит мимо файрвола и часто мимо аутентификации: многие внутренние сервисы доверяют всему, что пришло «изнутри».

Где появляется SSRF

Любая функция, которая принимает URL и что-то по нему скачивает:

  • загрузка аватара или картинки по ссылке;
  • превью ссылок в чатах и CMS;
  • вебхуки с адресом, который задаёт пользователь;
  • генерация PDF и скриншотов из HTML (встроенный браузер грузит ресурсы со страницы);
  • импорт фидов, RSS, файлов с внешнего URL;
  • парсинг XML с внешними сущностями (XXE часто ведёт к SSRF).

Что можно достать через SSRF

ЦельПример адресаЧто получает атакующий
Локальные сервисыhttp://127.0.0.1:6379, http://localhost:9200Redis, Elasticsearch, админки без пароля
Внутренняя сетьhttp://10.0.0.5/adminвнутренние API, CI, мониторинг
Metadata облакаhttp://169.254.169.254/временные ключи роли сервера, настройки, user-data
Сканированиеперебор портовкарта внутренней инфраструктуры по таймингам и ошибкам

Самый опасный сценарий — metadata-эндпоинт. В AWS по адресу 169.254.169.254 экземпляр получает временные учётные данные своей IAM-роли. Если SSRF позволяет прочитать ответ, атакующий уносит ключи и действует в облаке с правами сервера.

Даже blind SSRF, когда ответ не возвращается, полезен атакующему: можно сканировать сеть и дёргать внутренние эндпоинты, которые что-то меняют.

Как обходят фильтры

Проверка «в URL нет localhost и 127.0.0.1» не работает. Типичные обходы:

  • Альтернативная запись IP: 2130706433, 0x7f000001, 0177.0.0.1, 127.1, [::1], [::ffff:127.0.0.1].
  • Домен, который резолвится во внутренний IP — достаточно A-записи на 127.0.0.1 в своём DNS.
  • DNS rebinding: при проверке домен указывает на внешний IP, а при самом запросе — уже на внутренний.
  • Редиректы: разрешённый внешний URL отвечает 302 на http://169.254.169.254/, а HTTP-клиент послушно идёт следом.
  • Расхождения парсеров: http://allowed.com@evil.com/, обратные слэши, фрагменты — валидатор и HTTP-клиент понимают хост по-разному.
  • Другие схемы: file://, gopher://, dict://, если библиотека их поддерживает.

Как защититься

1. Allowlist вместо blocklist

Лучший вариант — не принимать произвольные URL. Если интеграция работает с конкретными сервисами, разрешите только их домены и схему https.

2. Если нужны произвольные URL

  • Разрешайте только http и https и стандартные порты.
  • Резолвите DNS сами, проверяйте все полученные IP и отклоняйте приватные, loopback, link-local и зарезервированные диапазоны.
  • Подключайтесь именно к проверенному IP, а не резолвите имя повторно — это закрывает DNS rebinding.
  • Отключите автоматические редиректы или проверяйте каждый переход заново.
  • Не возвращайте пользователю сырой ответ и подробные ошибки; ставьте таймауты и лимит размера.
import ipaddress, socket
from urllib.parse import urlparse

def resolve_public(url: str) -> set:
    u = urlparse(url)
    if u.scheme not in ("http", "https") or not u.hostname:
        raise ValueError("bad url")
    port = u.port or (443 if u.scheme == "https" else 80)
    ips = {ipaddress.ip_address(info[4][0])
           for info in socket.getaddrinfo(u.hostname, port)}
    if not all(ip.is_global for ip in ips):
        raise ValueError("internal address")
    return ips  # подключаться дальше только к этим IP

3. Изоляция сети

Код можно обойти, сеть — сложнее. Выносите скачивание внешних ресурсов в отдельный сервис или egress-прокси, у которого нет маршрута во внутреннюю сеть и к metadata. Добавьте правила исходящего трафика на уровне security groups или файрвола, а внутренние сервисы закройте аутентификацией, даже если они «не торчат наружу».

4. IMDSv2 и права в облаке

В AWS включите IMDSv2 в режиме required. Он требует сначала получить токен запросом PUT с особым заголовком, а простой GET через SSRF такого сделать не может. Ограничьте hop limit, чтобы контейнеры без необходимости не доходили до metadata, и выдавайте серверу IAM-роль с минимальными правами. В других облаках metadata тоже защищена обязательным заголовком, но изоляция сети остаётся главным слоем.

Подробный разбор — в OWASP SSRF Prevention Cheat Sheet.

Частые вопросы

Нет. Домен может резолвиться во внутренний IP, а парсер валидатора и HTTP-клиента могут по-разному понять один и тот же URL. Проверять нужно итоговый IP, к которому происходит подключение.

Поможем с этим

Если хотите не разбираться самим, а получить результат — этим мы занимаемся каждый день.

Обсудить проект

Читайте также

Есть идея или задача? Расскажите нам

Ответим в течение рабочего дня и предложим решение. Консультация бесплатная.