SSRF-атаки: как работает подделка серверных запросов
Продвинутое4 минРедакция Innovialabs
Коротко
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:9200 | Redis, 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.