SSRF через обход проверки IP с помощью HTTP-редиректа
Приложение проверяет IP адрес при первом запросе, но cURL автоматически следует редиректам на внутренние адреса — атакующий обходит проверку через промежуточный сервер.
hardphpPro
Задача
# SSRF через обход проверки IP с помощью HTTP-редиректа
## Сценарий
Интернет-магазин **SecureShop** содержит функцию предпросмотра ссылок: пользователь вводит URL в форму, сервер скачивает содержимое и возвращает превью. Эндпоинт `POST /fetch-url` обрабатывает запросы и использует cURL для загрузки.
Разработчики знали про класс уязвимостей **Server-Side Request Forgery (SSRF)** и попытались защититься: перед запросом резолвят имя хоста в IP-адрес и блокируют loopback-диапазон (`127.0.0.0/8`, `::1`). Прямой запрос к `http://127.0.0.1:8080/admin/stats` возвращает `403 Access denied`.
Однако реализация защиты — однократная: проверяется только начальный URL. cURL настроен с `CURLOPT_FOLLOWLOCATION = true`, что означает автоматическое следование 301/302 редиректам без повторной валидации адреса назначения. Это и есть **redirect-based SSRF bypass** — классический шаблон, когда фильтр охраняет «входную дверь», но не следит, куда ведёт цепочка перенаправлений.
## В чём суть атаки
HTTP-запрос — это не одно действие, а последовательность шагов: TCP-коннект, отправка request, получение response, при 3xx — повторный коннект к новому хосту. Защита, проверяющая только первый URL, оставляет «зазор» между «фильтр одобрил» и «сервер реально подключился к внутреннему ресурсу».
Атакующему нужны два элемента:
1. **Целевой внутренний эндпоинт** — `/admin/stats`, который содержит конфиденциальные данные (флаг, пароль администратора), но открыт только для запросов с `127.0.0.1`.
2. **Открытый редиректор** — публично доступный URL, который проходит первичную проверку и возвращает `302 Location: <внутренний адрес>`. В этой лабе роль «редиректора» играет встроенный эндпоинт `GET /redirect?target=URL`, доступный по адресу `0.0.0.0:8080` (тот же контейнер, но другой публичный IP проходит фильтр `127.`).
В реальной системе таким редиректором мог бы быть OAuth callback, partner-tracking URL, open-redirect в маркетинговом домене — везде, где разработчики позволяют контролируемый `Location`-заголовок.
## Цели
1. Авторизуйтесь как `demo` / `demo` и найдите форму предпросмотра ссылок.
2. Убедитесь, что прямой запрос с loopback-адресом (`http://127.0.0.1:8080/admin/stats`) блокируется фильтром.
3. Найдите эндпоинт, который умеет возвращать редирект на произвольный URL.
4. Постройте цепочку: внешне-валидный URL → 302 → внутренний адрес.
5. Извлеките CTF-флаг из JSON-ответа эндпоинта `/admin/stats`.
## Данные
| Параметр | Значение |
|----------|----------|
| Обычный пользователь | `demo` / `demo` |
| Внутренний эндпоинт с флагом | `/admin/stats` (доступен только с `127.0.0.1`) |
| Эндпоинт загрузки URL | `POST /fetch-url` (параметр `url`) |
| Встроенный редиректор | `GET /redirect?target=<URL>` (возвращает 302) |
> Подсказка: первичный фильтр проверяет, что резолвленный IP не начинается с `127.`. Но это не единственный способ адресовать сервер по loopback — есть алиасы и адреса-маркеры, которые проходят такую строковую проверку, но фактически указывают на ту же машину.