SSRF через file_get_contents() в загрузке аватара
Приложение загружает аватар по URL без проверки — атакующий может обратиться к внутренним сервисам.
easyphpPro
Задача
# SSRF: запрос к внутреннему эндпоинту через серверный HTTP-клиент
## Сценарий
Вы аудируете интернет-магазин **SecureShop**. На странице профиля доступна функция «Fetch Avatar»: пользователь указывает URL изображения, а сервер сам скачивает картинку по этому адресу и сохраняет её локально. Команда уверена, что эндпоинт безопасен — это просто прокси для загрузки внешних ресурсов. По данным внутреннего аудита, в приложении сохранился административный эндпоинт со статистикой и сервисной информацией, доступный только с loopback-интерфейса по проверке `REMOTE_ADDR`. В его ответе раскрывается конфиденциальная метка, которую требуется извлечь.
## Цель
1. Изучите эндпоинт предпросмотра аватара и поведение серверного HTTP-клиента: что именно сервер делает с пользовательским URL.
2. Используя серверный HTTP-клиент как прокси, обратитесь к административному эндпоинту, защищённому проверкой IP источника запроса.
3. Извлеките флаг из ответа административного эндпоинта.
## Теория
**Server-Side Request Forgery (SSRF)** — класс уязвимостей, при котором атакующий заставляет сервер выполнить HTTP-запрос по адресу, выбранному атакующим. Запрос исходит **от сервера**, поэтому имеет привилегии серверной сети: проходит файрволы, видит внутренние эндпоинты, использует loopback-интерфейс, а в облачных средах получает доступ к metadata-сервисам. SSRF превращает уязвимое приложение в прокси внутри доверительной границы.
**Уязвимый паттерн в PHP:**
```php
// Сервер делает запрос по URL, переданному пользователем, без проверки адреса
$url = $_POST['url'];
$content = file_get_contents($url); // file:// тоже сработает
// либо через cURL без CURLOPT_PROTOCOLS:
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$content = curl_exec($ch);
```
Ключевая ошибка — отсутствие проверки destination перед вызовом HTTP-клиента. Многие внутренние эндпоинты доверяют сетевой границе («доступно только с localhost», «доступно только из VPN», «доступно только из k8s-кластера»), но SSRF её обходит, потому что запрос всегда исходит из самой сети — от лица серверного процесса.
Классические цели SSRF: loopback (`127.0.0.1`, `::1`, `localhost`) — внутренние админ-панели и debug-эндпоинты; частные сети RFC 1918 (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`) — соседние сервисы кластера; cloud metadata (`169.254.169.254`) — IAM-credentials AWS / GCP / Azure; нестандартные схемы (`file://`, `gopher://`, `dict://`) — чтение локальных файлов или взаимодействие с redis/memcached на других портах.
## Точка входа атаки
`POST /fetch-url` — обработчик предпросмотра аватара принимает поле `url` и через `file_get_contents()` скачивает содержимое по этому адресу. Никакой валидации хоста и схемы не выполняется.
| Параметр | Значение |
|----------|----------|
| Учётные данные | `demo` / `demo` |
| Уязвимый эндпоинт | `POST /fetch-url` (поле `url`) |
| Цель | внутренний административный эндпоинт со статистикой |
| Где раскрывается флаг | JSON-ответ внутреннего эндпоинта, поле `config.flag` |