Веб
Уязвимости загрузки файлов и защита от них
Форма загрузки аватара или документа может открыть путь к выполнению кода на сервере. Практические правила: проверка типа, место хранения и именование файлов.
· 8 мин. чтения

Почти в каждом веб-приложении есть загрузка файлов: фото профиля, скан договора, Excel-отчёт, приложение к заявлению. Функция кажется простой, но при неправильной реализации превращается в одну из самых опасных уязвимостей: пользователь кладёт на сервер свой файл, а сервер его сохраняет и иногда даже выполняет.
OWASP отдельно описывает этот риск как «Unrestricted File Upload», а в CWE это CWE-434 (Unrestricted Upload of File with Dangerous Type). На экспертизе формы загрузки файлов, как правило, проверяют с особым вниманием.
Что может пойти не так
1. Выполнение кода на сервере
Самый тяжёлый случай. Пользователь загружает файл photo.php или photo.jpg.php, он попадает в публичную папку веб-сервера, затем пользователь открывает https://example.uz/uploads/photo.php — и PHP-код выполняется на сервере. Это может дать атакующему полный контроль над сервером.
2. Хранимый XSS
Внутри SVG и HTML-файлов может быть JavaScript. Если такой файл открывается в браузере с домена самого сайта, скрипт работает от имени этого сайта, с сессией пользователя.
3. Обход пути (path traversal)
Если имя файла выглядит как ../../config/app.php, а сервер подставляет его прямо в путь, файл может быть записан не туда, куда задумано, или перезаписать существующий.
4. Отказ в обслуживании
Очень большие файлы, слишком много файлов или архивы вроде «zip-бомбы» заполняют диск и память и замедляют сервер.
5. Доступ к чужим файлам
Если загруженные личные документы хранятся в публичной папке и имеют предсказуемые имена (passport_15.pdf), их могут открыть другие пользователи. По сути это разновидность IDOR.
Главное правило: загруженный файл — недоверенные данные. Никогда не выполняйте его, не доверяйте его имени и не храните его в публичной папке под именем, которое дал пользователь.
Слои защиты
Список разрешённых типов (allowlist)
Список запрещённых расширений («.php нельзя») не работает: всегда остаются .phtml, .php5, .phar и другие варианты. Вместо этого разрешайте только нужные типы и проверяйте реальное содержимое файла (MIME-тип), а не только расширение.
В Laravel:
$request->validate([
'avatar' => ['required', 'file', 'mimes:jpg,jpeg,png,webp', 'max:2048'],
'contract' => ['required', 'file', 'mimetypes:application/pdf', 'max:10240'],
]);
Правило mimes определяет MIME-тип по содержимому файла. max задаётся в килобайтах. Для изображений также полезны правила image и dimensions.
Имя файла задаёт сервер
Не используйте имя от пользователя для хранения. Генерируйте случайное имя, а исходное (если нужно) храните только в базе для отображения:
$path = $request->file('contract')->store('contracts', 'private');
// результат: contracts/7Hq2...xZ.pdf
Document::create([
'user_id' => $request->user()->id,
'path' => $path,
'original_name' => $request->file('contract')->getClientOriginalName(),
]);
store() даёт случайное имя и расширение, соответствующее содержимому, что заодно закрывает path traversal.
Храните вне публичной папки
Личные документы не должны лежать в public/. В Laravel используйте диск вроде storage/app/private и отдавайте файл через контроллер с проверкой владельца:
public function download(Document $document)
{
$this->authorize('view', $document);
return Storage::disk('private')->download(
$document->path,
$document->original_name
);
}
Тогда прямой ссылки на файл нет, и каждое скачивание проходит авторизацию.
Запретите выполнение скриптов в папке загрузок
Если изображения должны лежать в публичной папке (например, аватары), веб-сервер не должен выполнять в ней никаких скриптов. Для nginx:
location ^~ /storage/ {
location ~ \.(php|phtml|phar)$ { deny all; }
}
Это вторая линия защиты: даже если валидация по какой-то причине пропустит файл, он не выполнится как код.
Осторожно с SVG и HTML
Если SVG не нужен, не добавляйте его в список разрешённых. При отдаче пользовательских файлов ставьте безопасные заголовки:
add_header X-Content-Type-Options "nosniff" always;
Для скачиваемых документов заголовок Content-Disposition: attachment говорит браузеру не открывать файл как страницу. По возможности отдавайте пользовательские файлы с отдельного домена.
Перерабатывайте изображения
Перекодирование загруженного изображения (например, изменение размера и повторное сохранение) удаляет спрятанные внутри данные и метаданные (EXIF, включая GPS-координаты).
Ограничения размера и количества
Помимо правила max на уровне приложения, задайте лимиты на уровне веб-сервера и PHP:
client_max_body_size 10m;
upload_max_filesize = 10M
post_max_size = 12M
Добавьте ограничение частоты запросов (rate limiting) на маршруты загрузки.
Чек-лист
- Разрешены ли только нужные типы файлов и проверяются ли они по содержимому?
- Создаётся ли имя файла на стороне сервера?
- Хранятся ли личные документы вне публичной папки?
- Проверяется ли владелец при скачивании?
- Если открыть PHP-файл в папке загрузок, он выполнится или будет отклонён?
- Можно ли загрузить SVG и HTML, и как они отдаются?
- Есть ли лимит размера на уровне приложения, PHP и веб-сервера?
Последние пункты можно безопасно проверить самостоятельно: попробуйте загрузить безвредный test.php (например, просто выводящий текст) и посмотрите на результат — только в своей тестовой среде.
Итог
Безопасность загрузки файлов держится не на одном правиле, а на нескольких слоях: валидация, именование, место хранения, настройка веб-сервера и авторизация. Если один слой пропустит файл, его остановит следующий. В нашем процессе Claude AI агент быстро просматривает все точки загрузки, правила валидации и конфигурацию сервера, а специалист проверяет их на практике и вносит исправления.
Если хотите проверить безопасность форм загрузки в своём проекте, оставьте заявку на сканирование. Мы проверим их и поможем закрыть уязвимости до официальной экспертизы.


