Перейти к содержимому
← Каталог php File Upload

File Upload: Path Traversal через имя файла

Загрузка аватара использует оригинальное имя файла без санитизации. Подмени filename в multipart-запросе для перезаписи шаблона и получения RCE.

easyphpPro
Задача
# File Upload: подмена имени файла как канал записи произвольного пути ## Сценарий Интернет-магазин SecureShop позволяет авторизованным пользователям загружать собственный аватар на странице профиля. Загруженные файлы кладутся в директорию `/static/uploads/` внутри корня приложения, ссылка на аватар сохраняется в базе данных и подставляется в шаблон профиля. Команда разработки реализовала загрузку аватаров «в лоб»: серверный код получает файл из multipart-запроса и сохраняет его на диск, используя имя, пришедшее в заголовке `Content-Disposition`. Никакой защиты от путевых компонент в имени файла нет — разработчики посчитали, что браузер «всё равно отправит только базовое имя». По данным внутреннего аудита, на сервере хранится конфиденциальная метка в переменной окружения `LAB_FLAG`, которую можно прочитать из PHP-кода через `getenv("LAB_FLAG")`; ваша задача — её извлечь. ## Цель 1. Авторизуйтесь и откройте страницу профиля. Изучите multipart-поток, отправляемый формой загрузки аватара — какие части запроса полностью контролируются клиентом? 2. Найдите способ направить сохранение файла за пределы директории статики — в место, где PHP-интерпретатор подхватит загруженный контент при HTTP-запросе или при рендеринге следующих запросов. 3. Используйте этот канал, чтобы получить значение `LAB_FLAG` через серверное исполнение PHP. ## Теория Multipart-запрос включает заголовки для каждой части — в том числе `Content-Disposition` с полем `filename`, в которое браузер по умолчанию помещает оригинальное имя файла, выбранного пользователем. Это полностью **клиентский** ввод: атакующий, отправляющий запрос вручную (через curl, Burp, Python `requests`), может задать там произвольную строку — включая последовательности path traversal (`../`, абсолютные пути, кодированные эквиваленты). Начиная с PHP 8.1, помимо `$_FILES['avatar']['name']` (которое в исторических версиях обрезалось через `basename` на стороне интерпретатора) появилось дополнительное поле `$_FILES['avatar']['full_path']`. Оно хранит **полный путь**, переданный клиентом в `Content-Disposition`, **со всеми компонентами обхода каталога** — без какой-либо нормализации. Это поле было добавлено для поддержки сценария «загрузка папки» через `<input type="file" webkitdirectory>`, но в неосторожном коде превращается в прямой канал записи произвольного пути. **Уязвимый PHP-паттерн:** ```php // VULNERABILITY: full_path берётся напрямую из заголовка Content-Disposition, // сохраняя любые ../ из multipart-запроса $filename = $_FILES['avatar']['full_path'] ?? $_FILES['avatar']['name']; $uploadDir = __DIR__ . '/../../static/uploads/'; $destPath = $uploadDir . $filename; // Дополнительно: код самостоятельно создаёт промежуточные директории, // снимая последнее препятствие к записи в произвольное место $destDir = dirname($destPath); if (!is_dir($destDir)) { mkdir($destDir, 0775, true); } move_uploaded_file($_FILES['avatar']['tmp_name'], $destPath); ``` Самый «вкусный» класс целей для записи — файлы, которые приложение **читает и интерпретирует** при последующих запросах: PHP-шаблоны (`templates/*.php`, рендерятся обработчиками страниц), скрипты, лежащие в директориях, которые Apache отдаёт через PHP-интерпретатор, конфиги (если читаются на лету). PHP не кэширует исходники по умолчанию (opcache в dev-окружении отключён), поэтому перезаписанный файл подхватывается на следующем же HTTP-запросе. ### Таблица типичных целей для записи | Целевой путь | Триггер исполнения | Эффект | |--------------|-------------------|--------| | `templates/404.php` | любой запрос на несуществующий URL → `handleNotFound` рендерит шаблон | гарантированный | | `templates/profile.php` | `GET /profile` (страница профиля авторизованного пользователя) | RCE при логине | | `cmd/shell.php` | прямой `GET /cmd/shell.php` если директория отдаётся через Apache | зависит от DocumentRoot | | `static/uploads/shell.php` | прямой `GET /static/uploads/shell.php` | работает если PHP включён в /static | | `internal/handlers/handlers.php` | следующий любой запрос (обработчик берётся из этого файла) | полный takeover | В этой лабе типичная цель — перезапись `templates/404.php` или `templates/profile.php`: триггер исполнения легко спровоцировать без знания внутренней маршрутизации, а сами шаблоны гарантированно идут через PHP-интерпретатор. ## Точка входа атаки | Параметр | Значение | |----------|----------| | Учётные данные | `demo` / `demo` | | Форма загрузки аватара | страница профиля, поле `avatar`, multipart/form-data | | Уязвимый канал | заголовок `Content-Disposition: ...; filename="..."` части `avatar` (PHP 8.1+: `full_path`) | | Источник флага | переменная окружения `LAB_FLAG`, доступная через `getenv("LAB_FLAG")` из любого PHP-кода | | Финальная цель | вывести значение `LAB_FLAG` в HTTP-ответ через серверное исполнение загруженного PHP-файла |
🚧 Сайт в разработке. Полный функционал пока недоступен. Все вопросы — support@hackandfix.ru