Безопасная загрузка файлов в PHP — это не просто приём данных из формы и перемещение файла на сервер. Вложение может стать вектором выполнения кода, утечкой информации, нагрузкой, исчерпывающей ресурсы, или документом, недоступным в момент, когда он нужен бизнес-процессу. Проектирование должно охватывать полный цикл: приём, валидацию, хранение, обработку, авторизацию доступа, аудит и удаление.
Определите бизнес-контракт до приёма файлов

Первый контроль — не технический: необходимо ограничить, какую потребность решает каждое вложение. Документ, удостоверяющий личность, счёт и изображение профиля имеют разные форматы, владельцев, сроки хранения и права на просмотр. Объединение их в общий вариант «загрузить файл» затрудняет применение надлежащих контролей.
Для каждого типа документа определите явный контракт:
- Какие форматы необходимы и какие исключены.
- Максимальный размер, максимальное число вложений на операцию и накопительная квота для учётной записи или дела.
- Кто может его загрузить, на каком этапе процесса и может ли заменить или удалить его.
- Какая автоматическая валидация и какая ручная проверка ему нужны.
- Кто может его просматривать, скачивать или запрашивать новую версию.
- Как долго он хранится и какое событие инициирует его удаление.
Этот контракт предотвращает приём файлов «на всякий случай». Он также позволяет отличить ошибку валидации, которую пользователь может исправить, от бизнес-ограничения, например попытки прикрепить документ, когда дело уже закрыто.
Почему расширения и заявленного типа недостаточно
Расширение исходного имени и тип, переданный в $_FILES['type'], — это данные, предоставленные клиентом. Они могут служить вспомогательной информацией для интерфейса, но не подтверждают содержимое. Переименовать файл несложно, а клиент может отправить любые HTTP-заголовки.
Техническая валидация должна использовать многоуровневую защиту. В PHP определяйте тип по полученным байтам с помощью таких механизмов, как finfo; затем применяйте валидаторы, специфичные для формата, когда этого требуют риск или сценарий использования. Для изображения, которое будет отображаться, недостаточно определить его как изображение: рекомендуется декодировать его подходящей библиотекой и создать новое представление. Для PDF или офисного документа проверяйте ожидаемую структуру специализированными инструментами в изолированной среде.
Список разрешённых типов безопаснее списка запрещённых. Если сценарий использования допускает JPEG и PNG, отклоняйте всё остальное, а не пытайтесь перечислить опасные форматы. Сжатые файлы требуют дополнительного контроля: ограничение размера в сжатом виде не ограничивает размер после распаковки. Установите лимиты на размер в распакованном виде, число записей, глубину вложенности и время анализа.
Имена также ненадёжны. Не используйте их как путь, идентификатор или физическое имя. Последовательности вида ../, управляющие символы, двойные расширения и коллизии имён не должны иметь значения, поскольку система генерирует собственный непрозрачный идентификатор.
Контролируемый приём и обработка частичных ошибок
До обработки содержимого ограничьте поверхность ввода. Настройте согласованные лимиты в PHP, веб-сервере и обратном прокси. Если прокси принимает 100 МБ, а PHP допускает 10 МБ, поведение будет непонятным; если PHP разрешает больше предусмотренного, атакующий может исчерпать память, временное дисковое пространство или рабочие соединения.
Явно контролируйте размер каждого файла, общий размер запроса, число файлов и длительность загрузки. Проверяйте код ошибки каждой записи в $_FILES, удостоверяйтесь, что она получена через HTTP-загрузку, и обрабатывайте каждое вложение как независимую единицу. В операции с несколькими файлами заранее решите, будет ли результат атомарным или частичным. Если допускаются частичные результаты, ответ должен точно указывать, какой файл получен, какой отклонён и почему, не раскрывая внутренние детали сервера.
Не обрабатывайте файл непосредственно по пути, контролируемому запросом, и не полагайтесь на то, что прерванная загрузка безвредна. Неполные временные файлы необходимо очищать, а повторные попытки должны быть идемпотентными, когда продукт их поддерживает. Токен операции или ключ идемпотентности предотвращает создание нескольких эквивалентных вложений после повторных сетевых отправок.
Приватное хранение, карантин и обработка
Бинарные файлы не должны находиться в публичном каталоге приложения. Храните их в приватном хранилище с учётными данными минимальных привилегий и связывайте каждый объект с внутренним идентификатором, сгенерированным системой. База данных может хранить метаданные: владельца, бизнес-контекст, определённый тип, размер, криптографический хеш, статус, даты и политику хранения. Не сохраняйте ненужные персональные данные в имени или операционных журналах.
Надёжный поток разделяет приём и доступность:
- Приложение принимает файл и создаёт запись в статусе
pendienteилиen_cuarentena. - Бинарный файл помещается в место, недоступное для скачивания.
- Асинхронный процесс выполняет антивредоносное сканирование, углублённую валидацию, преобразование или разрешённое извлечение.
- Результат меняется на
disponible,rechazadoилиrequiere_revision. - Интерфейс показывает операционный статус, не создавая впечатления, что загрузка уже пригодна к использованию.
Очереди сокращают время ответа, но создают собственные сбои: дублирующиеся задачи, задержанные сообщения и остановленные обработчики. Проектируйте идемпотентные задачи, ограничения повторных попыток, оповещения о зависших элементах и контролируемый путь повторной обработки. Если анализ недоступен, безопасной альтернативой обычно является оставить вложение в карантине, а не публиковать его.
Авторизуйте каждое скачивание и выдавайте содержимое без выполнения
Само знание идентификатора не предоставляет доступ. При каждом скачивании необходимо проверять аутентифицированную личность, её текущую связь с ресурсом и бизнес-контекст: принадлежность к организации, привязку к делу, действующую роль, статус документа и временные ограничения. Не используйте повторно авторизацию, существовавшую при загрузке файла; права могли быть отозваны позднее.
Скачивание должно проходить через контроллер, который применяет это решение до чтения или делегирования объекта. Подписанные URL могут быть полезны для выдачи из внешнего хранилища, но требуют ограниченной области действия, короткого срока действия и контролей, предотвращающих их выпуск для неавторизованных ресурсов.
Выдавайте активные типы с осторожностью. Для документов, не предназначенных для рендеринга в браузере, используйте Content-Disposition: attachment. Устанавливайте тип содержимого на основе валидированного типа, а не расширения, и добавляйте X-Content-Type-Options: nosniff. Предпросмотр — это не простое встроенное скачивание: он должен использовать преобразованные представления, изолировать потенциально активное содержимое и не раскрывать внутренние пути.
Аудит, восстановление и тестирование перед вводом в эксплуатацию

Регистрируйте значимые события: создание, замену, изменение статуса, скачивание, удаление, ошибку анализа и изменение разрешений. Журнал должен содержать субъект, момент, ресурс и результат, но не обязан хранить бинарный файл или дублирующиеся чувствительные данные. Защитите эти события от изменений и определите, кто может их просматривать.
Восстановление требует понимания того, что происходит при сбое хранилища, базы данных или обработчика. Не отмечайте документ как доступный, пока бинарный файл и его метаданные не согласованы. Спроектируйте задачи сверки для выявления записей без объекта, объектов-сирот и элементов, остающихся в карантине слишком долго.
Чек-лист перед выпуском
- Разрешённые форматы соответствуют документированному сценарию использования и валидируются по содержимому.
- Существуют ограничения на размер, количество, время и распаковку сжатых файлов.
- Исходные имена не определяют пути или физические имена.
- Бинарные файлы хранятся вне публичного каталога и при необходимости проходят карантин.
- Скачивание повторно оценивает авторизацию и не раскрывает внутренние пути.
- Тестируются повреждённые файлы, дубликаты, прерванные загрузки, отозванные разрешения и сбои хранилища.
- Есть мониторинг отклонений, зависших задач, ошибок выдачи и потреблённой ёмкости.
- У политик хранения, удаления и аудита есть ответственный за эксплуатацию.
Преобразование загрузки в поток со статусами и контролями может казаться более затратным, чем использование каталога загрузок. Однако это сокращает число импровизированных решений при появлении подозрительного файла, запроса на доступ или операционного сбоя. Такая трассируемость — часть продукта, а не последующее дополнение.



