Перейти к основному содержанию
Блог

Веб

Уязвимости загрузки файлов и защита от них

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

· 8 мин. чтения

Фото: unspecified, CC0 · Wikimedia Commons

Почти в каждом веб-приложении есть загрузка файлов: фото профиля, скан договора, 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 агент быстро просматривает все точки загрузки, правила валидации и конфигурацию сервера, а специалист проверяет их на практике и вносит исправления.

Если хотите проверить безопасность форм загрузки в своём проекте, оставьте заявку на сканирование. Мы проверим их и поможем закрыть уязвимости до официальной экспертизы.