Перейти к содержанию
Innovialabs

Безопасная загрузка файлов: чек-лист для разработчика

Практика3 мин

Коротко

Загруженный файл — это недоверенные данные, которые нельзя ни исполнять, ни отдавать как есть. Проверяйте тип по allowlist и сигнатуре, ограничивайте размер, переименовывайте, храните вне web root, перекодируйте изображения и сканируйте на вирусы.

Главное правило

Любой файл от пользователя — недоверенный ввод. Имя, расширение, Content-Type и содержимое контролирует тот, кто его загружает. Опасных сценариев три:

  • исполнение кода на сервере, если файл попал туда, где веб-сервер его запускает (shell.php в /uploads/);
  • XSS и фишинг, если HTML или SVG отдаётся с вашего домена и открывается в браузере;
  • отказ в обслуживании — огромные файлы, «бомбы» в архивах и картинках.

Ниже — чек-лист, который закрывает все три.

1. Тип файла

  • Allowlist расширений, а не blocklist: jpg, png, pdf — и ничего сверх нужного. Список «запрещённых» всегда неполон (phtml, phar, shtml…).
  • Берите расширение после последней точки и приводите к нижнему регистру. photo.php.jpg и photo.JPG — частые обходы.
  • Content-Type из запроса не доказательство: его выставляет клиент. Проверяйте сигнатуру файла (magic bytes) библиотекой вроде libmagic или file-type.
  • Расширение, MIME и сигнатура должны совпадать. Расхождение — отказ.

2. Размер и количество

  • Лимит на уровне прокси (client_max_body_size в Nginx), приложения и хранилища — чтобы большой файл отсекался до того, как съест память.
  • Для изображений проверяйте размер в пикселях, а не только в байтах: маленький файл может распаковаться в гигантскую картинку.
  • Архивы распаковывайте с лимитом на итоговый объём и число файлов, а пути внутри архива проверяйте на ../ (zip slip).
  • Ограничьте число загрузок на пользователя и в единицу времени.

3. Где хранить

  • Вне web root или в объектном хранилище (S3-совместимом) с приватным бакетом. Веб-сервер не должен иметь возможности исполнить загруженный файл.
  • Отдавайте файлы через обработчик с проверкой прав или через подписанные ссылки с ограниченным сроком жизни.
  • Пользовательский контент лучше раздавать с отдельного домена (usercontent.example.net): даже если там окажется скрипт, он не получит cookie основного сайта.
  • Права на скачивание проверяйте так же строго, как на любые данные — иначе получится IDOR через /files/123.

4. Имя файла

  • Генерируйте имя сами (UUID или хеш) и храните оригинальное имя в базе.
  • Так закрываются path traversal (../../etc/passwd), перезапись чужих файлов, спецсимволы и слишком длинные имена.
  • Оригинальное имя при отдаче экранируйте в Content-Disposition, а в интерфейсе выводите как текст.

5. Изображения

  • Перекодируйте картинки: декодировать и сохранить заново через библиотеку обработки. Это удаляет посторонние данные, «полиглоты» (файл, валидный сразу в двух форматах) и метаданные EXIF, включая геолокацию.
  • Держите библиотеки обработки обновлёнными: у них регулярно находят уязвимости.
  • Обработку выполняйте в отдельном воркере или контейнере с ограничением ресурсов и без лишних сетевых доступов.

6. Антивирус

  • Сканируйте файлы (например, ClamAV) до того, как они станут доступны другим пользователям.
  • Удобная схема: файл попадает в карантин со статусом «на проверке», асинхронный воркер сканирует, и только затем файл публикуется.
  • Антивирус не заменяет пункты выше: он ловит известное вредоносное ПО, а не XSS в SVG.

7. SVG и HTML

SVG — это XML, внутри которого могут быть <script>, обработчики событий и внешние ссылки. Если SVG открывается с вашего домена, это хранимый XSS. Варианты:

ВариантКогда подходит
Запретить SVGесли без него можно обойтись
Конвертировать в PNGдля аватаров и превью
Санитизировать (например, DOMPurify на сервере)если векторный формат нужен
Отдавать с отдельного домена как attachmentдля хранения «как есть»

То же относится к HTML, XML и PDF с активным содержимым.

8. Заголовки при отдаче

Content-Type: application/pdf
Content-Disposition: attachment; filename="report.pdf"
X-Content-Type-Options: nosniff
Content-Security-Policy: default-src 'none'; sandbox

nosniff запрещает браузеру угадывать тип, а attachment заставляет скачать файл вместо открытия. Больше деталей — в OWASP File Upload Cheat Sheet.

Частые вопросы

Нет. Расширение и Content-Type задаёт клиент. Нужна проверка сигнатуры, хранение без возможности исполнения и безопасная отдача — иначе одна ошибка в конфигурации превращается во взлом.

Поможем с этим

Если хотите не разбираться самим, а получить результат — этим мы занимаемся каждый день.

Обсудить проект

Читайте также

Есть идея или задача? Расскажите нам

Ответим в течение рабочего дня и предложим решение. Консультация бесплатная.