File Upload Bypass через двойное расширение
Обработчик загрузки использует filepath.Ext() для проверки расширения, но эта функция возвращает только последнее расширение. Файл test.html.jpg проходит проверку, а Go http.FileServer может определить Content-Type по первому расширению.
mediumgolangPro
Задача
# File Upload: обход allowlist через множественные расширения в имени
## Сценарий
Вы тестируете интернет-магазин **SecureShop**: в приложении доступна загрузка медиа-файлов через API-эндпоинт. После предыдущего инцидента команда добавила «защиту» — фильтр расширений, разрешающий только изображения. Команда уверена, что allowlist расширений теперь полностью покрывает риск загрузки исполняемого контента. По данным внутреннего аудита, доступ к admin-функциональности приложения требует получения сессии с привилегиями — её требуется получить, имея только обычную учётную запись.
## Цель
1. Изучите, как именно сервер извлекает «расширение» из имени файла для allowlist-проверки.
2. Найдите способ передать файл, у которого «последнее расширение» соответствует allowlist, но реальное содержимое (или intermediate-расширение) — другого типа, исполняемого браузером.
3. Используя этот канал, получите доступ к admin-функциональности и извлеките флаг.
## Теория
Стандартный подход к проверке типа файла «по расширению» в Go — функция, возвращающая часть имени **после последней точки**. Это семантически корректное определение «расширения файла» по соглашению UNIX/Windows, но в роли защитного фильтра оно работает плохо: файл может иметь несколько разделённых точкой суффиксов в имени (`report.pdf.exe`, `photo.html.jpg`, `archive.tar.gz`), и в зависимости от того, кто и как его обрабатывает, разные части имени могут интерпретироваться по-разному.
**Уязвимый паттерн:**
```go
// "payload.html.jpg" -> filepath.Ext возвращает ".jpg" -> проходит allowlist
ext := filepath.Ext(header.Filename)
if !allowedExts[ext] { return /* reject */ }
io.Copy(out, file) // сохраняем оригинальное имя как есть
```
Атакующий пользуется тем, что сервер при сохранении хранит оригинальное имя `payload.html.jpg`. При последующей отдаче файла через `http.FileServer` — расширение становится контекстом для `Content-Type`-определения; разные веб-серверы и middleware-цепочки могут реагировать по-разному (Apache с `MultiViews` и `mod_mime` будет искать ассоциацию для **каждого** суффикса, и `.html` сделает файл HTML-документом). В Go-сценарии без `nosniff` — браузер при открытии URL применит MIME sniffing к содержимому; HTML-разметка внутри будет распознана и исполнена.
## Точка входа атаки
| Параметр | Значение |
|----------|----------|
| Учётные данные | `demo` / `demo` |
| Эндпоинт загрузки | `POST /api/upload` (multipart/form-data) |
| Директория отдачи | `/uploads/` (доступна по прямому URL) |
| Цель | получить access к admin-функциональности через канал, открытый загруженным файлом |