Загрузка файлов в веб-приложении: как защитить сервер и пользователей
Форма загрузки аватара, поле для резюме, кнопка «прикрепить документ» — каждая из них может стать входной точкой для атаки, если за ней нет правильно выстроенной защиты. CWE-434 (Unrestricted Upload of File with Dangerous Type) стабильно входит в списки наиболее эксплуатируемых слабостей, и атаки через загрузку файлов по-прежнему регулярно приводят к полному компрометированию серверов.
Почему загрузка файлов — особый вектор атаки
Загрузка пользовательских файлов — стандартная функциональность в подавляющем большинстве современных приложений. Медицинские порталы, CMS, HR-системы, мессенджеры, SaaS-платформы — везде есть хотя бы одна форма загрузки. При этом endpoint загрузки является точкой пересечения сразу нескольких уязвимых поверхностей: входная валидация, файловая система, библиотеки обработки файлов, конфигурация веб-сервера, политики доступа.
OWASP относит неограниченную загрузку файлов к классу уязвимостей с высоким impactом: атакующий может получить выполнение кода на сервере, использовать загруженный файл для клиентских атак (XSS, Cross-site Content Hijacking), разместить вредоносное ПО, а также эксплуатировать уязвимости в библиотеках обработки изображений или архивов на серверной стороне [1]. Слабость классифицируется по CWE-434 и исторически относится к CWE Top 25 наиболее опасных слабостей [2].
Четверть программных систем имеет как минимум одну высококритичную уязвимость безопасности, и файловые загрузки — одна из наиболее частых точек её появления [3].
Анатомия атаки через загрузку файлов
Атаки через загрузку файлов не монолитны. Понимание классификации помогает правильно расставить контрмеры.
1. Server-side атаки: webshell и RCE
Наиболее критичный сценарий: атакующий загружает исполняемый скрипт (PHP, JSP, ASPX) и размещает его в директории, доступной через URL. После этого достаточно одного HTTP-запроса, чтобы выполнить произвольный код на сервере с правами веб-процесса.
Именно этот паттерн стоит за одним из самых резонансных инцидентов 2025 года. CVE-2025-31324 в SAP NetWeaver Visual Composer — уязвимость с максимальным CVSS 10.0 — позволяла неаутентифицированным атакующим загружать произвольные исполняемые файлы через endpoint /developmentserver/metadatauploader, который не имел никаких проверок типа файла и авторизации [4]. Атаки были зафиксированы с апреля 2025 года: злоумышленники загружали JSP-webshell'ы напрямую в servletJSP-директорию. По данным Censys, более 1200 серверов SAP NetWeaver оказались скомпрометированы, в числе которых — системы компаний из Fortune 500 [5].
Аналогичная механика — в CVE-2025-52691 (SmarterMail): уязвимость позволяла загрузить .aspx webshell через endpoint /api/upload в сочетании с path traversal, что давало выход за пределы доверенной директории прямо в webroot [6].
2. Path traversal через имя файла
Атакующий передаёт имя файла вида ../../../etc/passwd или ../webroot/shell.php. Если приложение использует пользовательское имя файла для сохранения — файл попадает не туда, куда ожидалось. В сочетании с исполняемым контентом это сразу превращается в RCE [7].
В WordPress-плагине Ninja Forms (CVE-2026-0740) отсутствие валидации типа файла в функции handle_upload позволяло размещать PHP-файлы в любой директории, доступной по URL [8].
3. Client-side атаки: XSS через SVG
SVG — валидный XML-формат, поддерживающий теги <script> и обработчики событий (onload, onerror). Если приложение принимает SVG-файлы и отдаёт их с того же origin, что и основное приложение, встроенный JavaScript исполняется в браузере пользователя с полным доступом к сессии [9]. Это хранимый XSS, который обходит большинство стандартных CSP-политик, применённых к HTML-страницам.
4. Атаки через библиотеки обработки файлов
Уязвимость может находиться не в коде загрузки, а в библиотеке, которая обрабатывает файл после. Классический пример — ImageTragick (CVE-2016-3714): ImageMagick через механизм delegates передавал определённые форматы внешним программам через shell-команды, что позволяло исполнять произвольный код при обработке специально сконструированного «изображения» [10]. Аналогичные парсер-баги периодически появляются в libpng, libjpeg, libwebp и ffmpeg. Даже если загрузочный handler написан правильно, уязвимая зависимость делает всю цепочку ненадёжной.
5. DoS через ресурсоёмкие файлы
ZIP bomb, XML billion laughs attack, или просто загрузка файлов объёмом в десятки гигабайт — всё это способно исчерпать дисковое пространство, RAM или CPU сервера. Отдельный вектор: уязвимый SVG, вызывающий краш ImageMagick, может генерировать временные файлы объёмом в 100× от размера входного — специально созданный SVG 100 МБ порождает ~10 ГБ мусора на диске [11].
Почему поверхностной валидации недостаточно
Большинство разработчиков знают про проверку расширения файла. Но у атакующих есть набор хорошо известных техник обхода каждого из «простых» механизмов.
Расширение файла
Если приложение проверяет только расширение, атакующий использует двойное расширение: shell.php.jpg или shell.phtml. В зависимости от конфигурации Apache mod_php или Nginx такой файл может быть обработан как PHP. Null-byte injection (shell.php%00.jpg) эксплуатирует поведение C-строк в некоторых языках — файл сохраняется без суффикса .jpg.
Принципиальная рекомендация OWASP и PortSwigger: использовать allowlist расширений (явно разрешённые), а не denylist (явно запрещённые). Угадать, какие расширения безопасны — гораздо проще, чем предусмотреть все опасные [12].
Content-Type header
Браузер и любой HTTP-клиент могут отправить произвольное значение заголовка Content-Type. Проверка if (file.content_type == "image/jpeg") на серверной стороне — не защита, это просто чтение поля, которое контролирует атакующий [13].
Magic bytes
Magic bytes — первые байты файла, идентифицирующие его реальный формат. PNG начинается с \x89PNG\r\n, JPEG с \xFF\xD8\xFF, GIF с GIF89a и т.д. Проверка magic bytes значительно надёжнее расширений и Content-Type, но и она не абсолютна.
Атакующие применяют два подхода: подделку magic bytes (добавить GIF89a;\x0a в начало PHP-скрипта — и валидатор, проверяющий только заголовок, пропустит его) и создание полиглот-файлов, которые одновременно являются валидными файлами двух форматов. Например, файл, корректно парсящийся как изображение и одновременно содержащий исполняемый код [14].
Правильный подход — не ограничиваться проверкой первых байтов, а парсить полную структуру файла. Для изображений это означает запуск через ImageMagick (с актуальными policy.xml ограничениями) или аналогичный инструмент, который полностью разбирает файл как конкретный формат.
Многоуровневая защита: что и где ставить
Уровень 1: Валидация до сохранения
Все проверки должны выполняться на сервере. Клиентская валидация — удобство для пользователя, не защита: она обходится через DevTools, curl или Burp Suite за секунды.
Чек-лист валидации:
- Allowlist расширений: только явно разрешённые типы, исходя из бизнес-требований. Никаких
.php,.phtml,.asp,.aspx,.sh,.exe,.bat,.cgi,.jsp,.py,.rbи производных. - Проверка magic bytes через библиотеку (python-magic, Apache Tika, file-type для Node.js). Не доверять Content-Type header.
- Полный парсинг структуры файла через специализированную библиотеку (для изображений — PIL/Pillow с перекодированием, для PDF — pdfminer или PDFBox).
- Ограничение размера файла: устанавливать максимум на уровне сервера (
upload_max_filesizeв PHP,client_max_body_sizeв Nginx) и проверять до начала обработки. - Санитизация имени файла: никаких пользовательских имён в путях файловой системы. Генерировать случайный UUID как имя для хранения.
- Ограничение длины имени файла и допустимых символов.
Уровень 2: Переименование и хранение
OWASP File Upload Cheat Sheet рекомендует иерархию по приоритету безопасности [15]:
- Хранить файлы на отдельном хосте — полное разделение между приложением и сервисом хранения.
- Хранить файлы за пределами webroot — прямой доступ по URL исключён.
- Если файлы должны быть в webroot — только права на запись, без выполнения.
Практическое правило: загруженный файл profile.jpg никогда не должен сохраняться под этим именем. Сервер присваивает случайный идентификатор (a7f3c2d1-4b8e-...jpg), а оригинальное имя сохраняется только в базе данных для отображения пользователю.
Уровень 3: Запрет выполнения в директории загрузок
Конфигурация веб-сервера должна явно запрещать выполнение скриптов в директории, куда попадают файлы.
Для Apache — через .htaccess:
php_flag engine off
Options -ExecCGI
AddType text/plain .php .php3 .php4 .php5 .phtml .pl .py .jsp .asp .sh .cgiДля Nginx — в конфиге location:
location ~* /uploads/.*\.(php|phtml|pl|py|jsp|asp|sh|cgi)$ {
deny all;
}Это дополнительный рубеж: даже если файл прошёл неверную валидацию и попал в директорию загрузок — сервер не выполнит его.
Уровень 4: Сканирование на малварь
Для приложений, принимающих файлы от внешних пользователей, особенно в enterprise- и healthcare-контексте, рекомендуется антивирусное сканирование загруженного контента. На AWS для этого с июня 2024 года доступен Amazon GuardDuty Malware Protection for S3 — он сканирует объекты на лету при загрузке в S3-бакет и расставляет теги NO_THREATS_FOUND / THREATS_FOUND, по которым можно автоматически маршрутизировать файлы в чистый или карантинный бакет [16].
Альтернативный подход — CDR (Content Disarm & Reconstruct): вместо поиска известных сигнатур, файл разбирается до базовых компонентов и пересобирается без потенциально вредоносных элементов. OWASP File Upload Cheat Sheet явно рекомендует CDR для форматов, поддерживающих макросы и встроенные объекты (PDF, DOCX) [15].
Уровень 5: Раздача файлов пользователям
Загруженные файлы должны раздаваться через приложение с явными заголовками, а не через прямой URL к директории хранения.
Критически важные заголовки при отдаче файлов:
Content-Disposition: attachment; filename="..."— предотвращает inline-рендеринг в браузере.Content-Typeс корректным значением, определённым сервером по реальному содержимому (не из базы данных или запроса пользователя).X-Content-Type-Options: nosniff— запрещает браузеру угадывать MIME-тип.
Если приложение принимает SVG и раздаёт их с того же origin — это потенциальный вектор XSS. Рекомендуется или отклонять SVG, или раздавать с заголовком Content-Disposition: attachment, или использовать отдельный изолированный домен (например, static.example.com вместо app.example.com).
Уровень 6: Аутентификация, авторизация и rate limiting
Загрузка файлов должна быть доступна только аутентифицированным пользователям — если это не противоречит бизнес-требованиям. CVE-2025-31324 в SAP NetWeaver — классический пример того, что происходит при отсутствии проверки авторизации на upload-endpoint: уязвимость позволяла загружать файлы без какой-либо аутентификации [4].
Дополнительно: ограничение частоты загрузок (rate limiting) по пользователю и IP предотвращает DoS через массовую загрузку файлов. OWASP API Security Top 10 2023 выделяет Unrestricted Resource Consumption как отдельную категорию рисков именно по этой причине [17].
Специфика обработки изображений
Изображения — самый распространённый тип загружаемых файлов и при этом нетривиальный вектор атак.
Безопасная работа с изображениями требует:
- Перекодирование через PIL/Pillow (Python) или аналог: открыть файл как изображение, создать новый объект в памяти и сохранить его. Этот процесс разрушает вредоносные embedded-данные, сохраняя при этом само изображение.
- Удаление EXIF-метаданных: они могут содержать GPS-координаты, данные об устройстве, а в некоторых библиотеках — и встроенный исполняемый код.
- Актуальная версия ImageMagick с корректно настроенным
policy.xml: ограничения по форматам, кодекам и ресурсам существенно снижают attack surface библиотеки [9].
ZIP и архивы: zip bomb и path traversal
ZIP-файлы требуют отдельного внимания по двум причинам.
Первая — zip bomb: небольшой архив (несколько КБ), при распаковке которого получаются терабайты данных. OWASP Input Validation Cheat Sheet рекомендует проверять уровень сжатия и предполагаемый размер после распаковки до начала операции [18].
Вторая — zip slip: архив содержит файлы с именами вида ../../etc/cron.d/malicious. Уязвимые библиотеки распаковки следуют этому пути и записывают файлы за пределами целевой директории. Перед записью каждого файла из архива необходимо проверять, что канонический путь назначения начинается с ожидаемой директории.
Реальные кейсы: что происходит при отсутствии защиты
SAP NetWeaver (CVE-2025-31324, апрель 2025)
Один endpoint без проверки типа файла и авторизации — CVSS 10.0, более 1200 скомпрометированных серверов по всему миру, среди которых системы компаний Fortune 500. Атакующие (предположительно, China-nexus группа) загружали JSP webshell'ы, устанавливали C2-фреймворк Brute Ratel и проводили пост-эксплуатацию. EclecticIQ задокументировала 581 подтверждённый случай компрометации в одном файле на инфраструктуре атакующих [19].
WordPress плагины (CVE-2025-23921, CVE-2026-0740)
Upload-плагины для WordPress — постоянная цель атак. CVE-2025-23921 (Multi Uploader for Gravity Forms) активно эксплуатировался с августа 2024 года: атакующие загружали .phtml webshell'ы, маскируя их под изображения. Атаки фиксировались с нескольких континентов одновременно, что указывает на глобальные автоматизированные кампании [20]. CVE-2026-0740 в Ninja Forms File Uploads — та же механика: отсутствие проверки типа файла в handle_upload давало выход к RCE [8].
SmarterMail (CVE-2025-52691)
Комбинация загрузки файла без валидации типа и path traversal в параметре contextData позволяла записать .aspx webshell в webroot-директорию. Без аутентификации. Уязвимость полностью устранена в Build 9413 [6].
Чек-лист: что проверять до merge и до релиза
Если в PR появляется код, связанный с загрузкой файлов, нужно проверить следующее:
Валидация:
- Есть ли allowlist расширений на серверной стороне?
- Проверяется ли тип файла по magic bytes, а не только по Content-Type header?
- Ограничен ли максимальный размер файла?
- Санитизируется ли имя файла (запрет
../, спецсимволов, double extension)?
Хранение:
- Сохраняется ли файл под случайным именем (не под пользовательским)?
- Находится ли директория хранения за пределами webroot?
- Запрещено ли выполнение скриптов в директории загрузок?
Раздача:
- Используется ли
Content-Disposition: attachment? - Установлен ли
X-Content-Type-Options: nosniff? - Определяется ли Content-Type сервером, а не из запроса пользователя?
Авторизация и ресурсы:
- Проверяется ли аутентификация и авторизация на upload-endpoint?
- Есть ли rate limiting по пользователю/IP?
- Актуальны ли библиотеки обработки файлов (ImageMagick, libpng, ffmpeg)?
Специфика:
- Принимает ли приложение SVG? Если да — раздаётся ли с отдельного домена или как attachment?
- Поддерживаются ли архивы (ZIP)? Если да — есть ли защита от zip bomb и zip slip?
AI-кодинг и загрузка файлов: специфический риск
Загрузка файлов — функциональность, которую AI-ассистенты генерируют часто и уверенно. Проблема в том, что сгенерированный код обычно «работает» — файл принимается, сохраняется, раздаётся. Но «работает» не означает «безопасно».
Типичные ошибки в AI-сгенерированном коде загрузки:
- Проверка только Content-Type без валидации magic bytes.
- Сохранение файла под пользовательским именем.
- Хранение в директории внутри webroot.
- Отсутствие ограничения по размеру.
- Использование пользовательского имени файла при построении пути (
os.path.join(upload_dir, filename)— классический path traversal).
PR с таким кодом пройдёт обычный review: сервис работает, тесты зелёные, логика читаема. Проблема видна только при анализе через призму безопасности.
Именно здесь полезен дополнительный слой контроля до merge — инструмент, который анализирует изменения в коде в контексте безопасности и сигнализирует о потенциально опасных паттернах ещё на стадии PR. HighVibe — один из таких инструментов: он не заменяет SAST или code review, а добавляет фокусированный сигнал по конкретному изменению, особенно важный для кода, написанного с AI-ассистентом.
Тренды и куда движется угроза
Автоматизация атак на upload-endpoint. CVE-2025-23921 показал: атаки идут непрерывно с первых дней после публичного раскрытия уязвимости и продолжаются месяцами против непропатченных систем. Злоумышленники используют автоматические сканеры и обновляют payload-базы по мере выхода новых CVE [20].
Сложные цепочки. SAP NetWeaver 2025 год продемонстрировал тренд на комбинацию: незащищённая загрузка файлов + insecure deserialization + privilege escalation в единой цепочке атаки, где каждый шаг усиливает следующий [4].
Supply chain через upload-библиотеки. Уязвимости во вспомогательных библиотеках (Multer, Paperclip, Shrine, аналоги) создают риск по всем приложениям, использующим их, независимо от качества кода самого приложения. Отслеживание advisory для npm, PyPI и других реестров по зависимостям обработки файлов — часть Security hygiene.
CDR как стандарт для enterprise. Content Disarm & Reconstruct из нишевой технологии становится ожидаемым контролем для организаций, принимающих Office-документы и PDF от внешних пользователей.
Заключение
Загрузка файлов — функциональность с обманчиво простым интерфейсом и нетривиальной поверхностью атак. Отсутствие валидации на upload-endpoint превращается в критическую уязвимость с CVSS 10.0 — это не абстрактный риск, это SAP NetWeaver в 2025 году.
Правильная защита строится послойно: allowlist расширений → проверка magic bytes → переименование файла → хранение за пределами webroot → запрет выполнения → безопасная раздача с корректными заголовками → аутентификация и rate limiting → сканирование и CDR для критичных контекстов.
Для команд, работающих с AI-кодингом, важно помнить: сгенерированный upload-handler может выглядеть корректно и работать без ошибок, но содержать уязвимости в валидации типа файла, именовании или месте хранения. Такой код проходит обычный review незамеченным. Дополнительный слой контроля на уровне PR — не роскошь, а практическая необходимость.
Что делать дальше:
- Провести аудит всех upload-endpoint в своих приложениях по чек-листу выше.
- Убедиться, что библиотеки обработки файлов (ImageMagick, ffmpeg, libpng) обновлены и имеют настроенные политики ограничений.
- Настроить запрет выполнения скриптов в директориях загрузок на уровне конфигурации веб-сервера.
- Для AI-сгенерированного кода загрузки добавить проверку в pipeline до merge.
Источники
[1] OWASP Foundation — "Unrestricted File Upload" — https://owasp.org/www-community/vulnerabilities/Unrestricted_File_Upload
[2] MITRE CWE — "CWE-434: Unrestricted Upload of File with Dangerous Type" — https://cwe.mitre.org/data/definitions/434.html
[3] Glasswall — "Best practice guide: protecting file uploads" — https://www.glasswall.com/blog/best-practice-guide-protecting-file-uploads
[4] Help Net Security — "Critical SAP NetWeaver flaw exploited (CVE-2025-31324)" — https://www.helpnetsecurity.com/2025/04/28/sap-netweaver-cve-2025-31324-exploited/
[5] Censys — "Advisory: CVE-2025-31324" — https://censys.com/advisory/cve-2025-31324/
[6] Medium / ro0taddict — "Replicating CVE-2025-52691: RCE via Unauthenticated Arbitrary File Upload in SmarterMail" — https://rodelllemit.medium.com/replicating-cve-2025-52691-an-rce-via-unauthenticated-arbitrary-file-upload-critical-vulnerability-e6c796270192
[7] Xygeni — "Path Traversal in File Uploads: How Developers Create Their Own Exploits" — https://xygeni.io/blog/path-traversal-in-file-uploads-how-developers-create-their-own-exploits/
[8] SentinelOne Vulnerability Database — "CVE-2026-0740: Ninja Forms File Uploads RCE" — https://www.sentinelone.com/vulnerability-database/cve-2026-0740/
[9] Eliran Turgeman — "How to Not Get Hacked Through File Uploads" — https://www.eliranturgeman.com/2026/03/14/uploads-attack-surface/
[10] ImageTragick — "ImageMagick Vulnerability Details" — https://imagetragick.com/
[11] GitHub Advisory Database — "ImageMagick SVG DoS (GHSA-gv85-xg33-553c)" — https://github.com/advisories/GHSA-gv85-xg33-553c
[12] PortSwigger Web Security Academy — "File Upload Vulnerabilities" — https://portswigger.net/web-security/file-upload
[13] Transloadit — "Secure API file uploads with magic numbers" — https://transloadit.com/devtips/secure-api-file-uploads-with-magic-numbers/
[14] Aardwolf Security — "File Upload Vulnerabilities: Complete Security Guide" — https://aardwolfsecurity.com/understanding-file-upload-vulnerabilities/
[15] OWASP Cheat Sheet Series — "File Upload Cheat Sheet" — https://cheatsheetseries.owasp.org/cheatsheets/File_Upload_Cheat_Sheet.html
[16] AWS Security Blog — "Using Amazon GuardDuty Malware Protection to scan uploads to Amazon S3" — https://aws.amazon.com/blogs/security/using-amazon-guardduty-malware-protection-to-scan-uploads-to-amazon-s3/
[17] OWASP API Security Top 10 2023 — "API4: Unrestricted Resource Consumption" — https://owasp.org/API-Security/editions/2023/en/0xa4-unrestricted-resource-consumption/
[18] OWASP Cheat Sheet Series — "Input Validation Cheat Sheet" — https://cheatsheetseries.owasp.org/cheatsheets/Input_Validation_Cheat_Sheet.html
[19] EclecticIQ — "China-Nexus Nation State Actors Exploit SAP NetWeaver (CVE-2025-31324)" — https://blog.eclecticiq.com/china-nexus-nation-state-actors-exploit-sap-netweaver-cve-2025-31324-to-target-critical-infrastructures
[20] TXOne Networks — "Persistent Exploitation of WordPress Multi Uploader Plugin (CVE-2025-23921)" — https://www.txone.com/blog/wordpress-multi-uploader-plugin-exploitation/