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

File Upload: Content-Type Bypass

Загрузка файлов проверяет только расширение, но не содержимое. Атакующий может загрузить HTML/JS в файле с расширением .jpg.

mediumphpPro
Задача
# File Upload: обход проверки типа через MIME sniffing ## Сценарий Интернет-магазин SecureShop позволяет авторизованным пользователям загружать в профиль изображения — стандартная функция «добавить фото для аватара». Команда разработки **добавила проверку формата** загружаемых файлов: явно перечислила, какие расширения изображений разрешены (`.jpg`, `.jpeg`, `.png`, `.gif`, `.webp`), и сохранила контент в директорию `/uploads/`, доступную всем авторизованным пользователям по прямой ссылке. Разработчики уверены, что фильтр расширений достаточен — «если расширение похоже на картинку, значит это картинка». Проверка реализована «дешёво»: смотрит только на имя файла, реальное содержимое в момент загрузки не валидируется. В дополнение к этому, при отдаче файлов из `/uploads/` сервер не выставляет заголовок, запрещающий браузеру самостоятельно определять тип содержимого, — рассчитывая, что `Content-Type` из ответа браузер примет на веру. ## Цель 1. Авторизуйтесь и найдите форму загрузки изображения в профиле. 2. Изучите проверки сервера: что именно валидируется — имя или содержимое? Какие подсказки даёт ответ при загрузке файла с «правильным» расширением, но необычным содержимым? 3. Сформируйте файл, который пройдёт фильтр, но при последующем обращении к нему браузер интерпретирует **не как изображение**. 4. Используйте полученный канал stored XSS на доверенном домене для извлечения CTF-флага. ## Теория В PHP-приложениях контроль загруженных файлов опирается на два независимых свойства: - **Расширение в имени файла** — строка, которая полностью контролируется клиентом. `pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION)` извлекает её из заголовка `Content-Disposition`, который атакующий может задать вручную. - **Магические байты содержимого** — сигнатура в первых байтах файла (например, `FF D8 FF` для JPEG, `89 50 4E 47` для PNG). Эти байты определяет источник файла; клиент-фильтр в браузере на них не смотрит, но сервер может — через `finfo` или `mime_content_type`. **Уязвимый PHP-паттерн (проверка только расширения):** ```php // VULNERABILITY: фильтр опирается на клиентское имя файла $ext = strtolower(pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION)); if (!in_array($ext, ['jpg', 'jpeg', 'png', 'gif', 'webp'], true)) { // отклоняем } // сохраняем как есть, содержимое не валидируется move_uploaded_file($_FILES['file']['tmp_name'], $uploadDir . $storedName); ``` Когда сервер ещё и **не отправляет** заголовок `X-Content-Type-Options: nosniff` при отдаче файла обратно, браузер применяет механизм **MIME sniffing** — анализирует начальные байты тела ответа, замечает разметку `<html>`/`<script>` и переключается из режима «картинка» в режим «HTML-страница», игнорируя присланный `Content-Type: image/jpeg`. Полезная нагрузка, лежащая внутри «изображения», исполняется в контексте домена приложения как обычный HTML — это и есть stored XSS на канале загрузки. Класс уязвимости — Unrestricted File Upload (CWE-434), частный случай Content-Type Bypass / MIME Type Confusion. ### Таблица типичных обходов фильтра расширений | Техника обхода | Идея | Когда сработает | |----------------|------|-----------------| | Спуфинг расширения | HTML-файл сохранён с именем `evil.jpg` | Сервер проверяет только расширение | | Magic bytes spoofing | Перед HTML вставлен валидный заголовок JPEG (`FFD8FF`) | Сервер использует `getimagesize` (которая смотрит только заголовок) | | Polyglot файл | Файл одновременно валидный JPEG и валидный HTML | Содержимое не пересохраняется, валидация только верхнего слоя | | SVG с `<script>` | Векторное изображение с JS-кодом | Сервер принимает `image/svg+xml` и отдаёт inline | | Двойное расширение | `evil.php.jpg` | Apache `AddHandler` обрабатывает по любому совпадению в имени | | Null-byte в имени | `evil.php%00.jpg` | Старые PHP-версии до 5.3.4 (для контекста) | В этой лабе срабатывает первый сценарий — спуфинг расширения с MIME sniffing'ом на стороне браузера. ## Точка входа атаки | Параметр | Значение | |----------|----------| | Учётные данные обычного пользователя | `demo` / `demo` | | Учётные данные привилегированного пользователя (моделирование результата cookie theft) | `admin` / `SuperSecret` | | Форма загрузки | страница `/profile`, поле `file`, multipart/form-data | | Прямая ссылка на загруженный файл | путь вида `/uploads/{hash}.{ext}` | | Источник флага | переменная окружения `LAB_FLAG`, выводится в карточке «CTF Flag Acquired!» в профиле после покупки CTF Flag в каталоге |
🚧 Сайт в разработке. Полный функционал пока недоступен. Все вопросы — support@hackandfix.ru