File Upload Bypass через подмену Content-Type
Обработчик загрузки файлов проверяет только расширение (.jpg, .png, .gif), но не реальное содержимое файла. HTML-файл с расширением .jpg будет принят и отдан браузеру.
mediumgolangPro
Задача
# File Upload: подмена типа файла через несоответствие расширения и содержимого
## Сценарий
Вы тестируете интернет-магазин **SecureShop**: в приложении доступна загрузка медиа-файлов (аватары, изображения товаров) через API-эндпоинт. Загруженные файлы становятся доступны по прямому URL под директорией `/uploads/`. Команда уверена, что эндпоинт безопасен — на сервере есть фильтр расширений, разрешающий только изображения. По данным внутреннего аудита, доступ к админ-панели приложения требует флаг авторизации, который можно получить через специфичный сценарий — её требуется получить, имея только обычную учётную запись.
## Цель
1. Изучите, что именно сервер проверяет при загрузке файла: имя/расширение или реальный контент.
2. Найдите способ загрузить файл, который **сервер посчитает изображением** на основе своих проверок, но при отдаче **браузер интерпретирует как другой тип контента** — открыв канал для активного исполнения.
3. Используя этот канал, получите доступ к админ-функциональности и извлеките флаг.
## Теория
Безопасная обработка загрузок файлов требует одновременной валидации **трёх независимых аспектов**: (1) имя файла (расширение, отсутствие path traversal), (2) реальный контент (magic-bytes — первые байты, идентифицирующие формат), (3) поведение при отдаче (`Content-Type` заголовок, `X-Content-Type-Options: nosniff`). Если защита покрывает только один аспект — обычно проще всего расширение — атакующий легко проходит фильтр, подавая файл с «правильным» расширением, но фактически содержащий другой тип контента.
**Уязвимый паттерн:**
```go
// Проверка только расширения — содержимое не валидируется
ext := strings.ToLower(filepath.Ext(header.Filename))
if !allowedExts[ext] {
return // reject
}
io.Copy(out, file) // сохраняем содержимое как есть
```
Усиливающий фактор — поведение браузера: при отдаче файла без явного `Content-Type` (или с общим `application/octet-stream`) браузер может выполнить **MIME sniffing**: посмотреть на первые байты содержимого и решить, что это HTML, в результате применяя HTML-парсер с исполнением встроенных скриптов. Заголовок `X-Content-Type-Options: nosniff` отключает это поведение, но если он не установлен, любой загруженный файл становится потенциальным каналом для XSS под доменом самого приложения — со всеми привилегиями same-origin.
## Точка входа атаки
| Параметр | Значение |
|----------|----------|
| Учётные данные | `demo` / `demo` |
| Эндпоинт загрузки | `POST /api/upload` (multipart/form-data) |
| Директория отдачи | `/uploads/` (статика) |
| Цель | получить access-флаг через канал stored XSS под доменом приложения, дающий доступ к admin-функциональности |