Запрет PHP в uploads: настройка без ложной защиты
АйТи Фреш
Информационная безопасность

Как закрыть каталог загрузок от исполнения скриптов

Автор: , директор ООО «АйТи-Фреш» · · ~11 мин чтения
Файлы загрузок отделены от механизма исполнения кода
Файлы хранятся, код не запускается.

Само наличие PHP-файла в uploads не означает взлом. Риск появляется, если приложение позволяет туда записать файл, а конфигурация веб-сервера передаёт его интерпретатору. Я покажу, как разорвать эту цепочку, проверить результат и не принять ошибку 403 за доказательство защиты.

Когда каталог загрузок становится точкой риска

Каталог загрузок нужен для файлов, которые пользователи добавляют через сайт: изображений, документов и других разрешённых форматов. В WordPress часто встречается путь wp-content/uploads; у других CMS он может называться иначе. Само имя каталога ничего не гарантирует. Важно, доступен ли он из браузера и может ли серверный обработчик запустить размещённый там код. Если каталог недоступен извне, это меняет сценарий, но не отменяет проверку прав и настроек.

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

Проверка расширения и MIME-типа тоже не даёт абсолютной гарантии: клиент может передать неверное значение, а содержимое и имя файла требуют серверной проверки. Поэтому в приложении задают список допустимых типов, генерируют безопасные имена, ограничивают размер и проверяют содержимое подходящими средствами. Если возможность позволяет, файлы хранят вне webroot или на отдельном хранилище. OWASP отдельно рекомендует рассматривать хранение вне webroot и ограничивать доступ к загруженным данным. Ни одна проверка по одной лишь строке расширения не заменяет эти меры.

Схема запрета исполнения файла из загрузок
Загрузка не должна запускать код.

Права доступа: кому нужно читать и писать

В Linux у файла и каталога есть владелец, группа и биты доступа. Но имя пользователя веб-процесса зависит от системы и конфигурации: встречаются www-data, nginx, apache и отдельные учётные записи сайта. Нельзя назначать владельца или группу наугад по примеру из чужой инструкции. Сначала выясните, под какой учётной записью работает PHP-FPM и каким пользователем выполняются обновления и загрузки. У Apache с разными MPM и схемами PHP модель также может отличаться.

Разделите необходимые действия. Если сайт принимает загрузки через PHP, процесс приложения должен иметь возможность создать файл в конкретном каталоге; это не означает, что тому же пользователю нужна запись во всём дереве сайта. Если загрузки выполняются при развёртывании через SFTP, веб-процесс может вообще не нуждаться в записи. Настройка должна сохранять требуемый сценарий CMS, но ограничивать область изменения. Нельзя просто снять запись у веб-пользователя и объявить работу сделанной: формы загрузки или обновления могут перестать работать.

Права 644 для файла и 755 для каталога часто встречаются как начальные ориентиры, однако это не универсальный рецепт: результат зависит от владельца, группы, ACL, umask и размещения сайта. Важно проверять эффективный доступ, включая родительские каталоги. Запись в .htaccess тоже должна быть ограничена теми, кому действительно нужно менять конфигурацию; конкретные цифры доступа выбирают под принятую схему управления. Не выдавайте 777 ради быстрого устранения ошибки: это маскирует причину и расширяет круг процессов, способных менять файлы. В документации WordPress отдельно сказано, что загрузочный каталог может располагаться не по стандартному пути, а сами каталоги сайта нельзя без разбора переводить в режим 777 — это относится и к uploads. Поэтому сначала определите фактический путь и владельца, а не копируйте chmod из форумного ответа.

Сегментация учётных записей и каталогов уменьшает последствия ошибки, но не отменяет запрет исполнения. Если PHP-процесс может записывать пользовательские данные в uploads, то при компрометации приложения злоумышленник может создавать там файлы. Серверная конфигурация должна независимо от этого не передавать такие запросы интерпретатору. Я проверяю обе стороны: способность записать разрешённый файл и невозможность исполнить скрипт по веб-запросу.

Давайте приложению только те права записи, которые нужны его рабочему сценарию, и только в нужных каталогах.

Разбираем пример без выдуманной истории взлома

Возьмём условный пример из поля company: сервис аренды костюмов «Маскарадный зал», 15 рабочих мест. Это иллюстрация, а не подтверждённый инцидент. Представим, что сайт хранит фотографии костюмов в каталоге загрузок. Администратор замечает неизвестный файл с расширением .php и видит обращения к нему в журнале. По одному имени нельзя заключить, что файл — веб-шелл, а по наличию запроса нельзя заключить, что код исполнился. Нужны копия файла для анализа, журналы приложения и веб-сервера, временная шкала и сведения о конфигурации.

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

В такой ситуации запрет исполнения в uploads снижает последствия конкретного способа размещения кода, но не говорит, как файл появился. Причиной может быть ошибка CMS или расширения, украденная учётная запись, небезопасная распаковка архива либо ручная загрузка. Надо обновить уязвимый компонент, проверить остальные каталоги, сопоставить журналы и оценить резервные копии. Сам факт, что PHP не исполняется в одном каталоге, не доказывает, что сайт чист: вредоносный код мог попасть в другой путь или базу данных.

Бэкап тоже не является автоматически чистым только потому, что его сделали до обнаружения проблемы. Для выбора точки восстановления сопоставьте время появления признаков, журналы резервирования и содержимое копии; проверьте восстановление в изолированной среде. Частота копирования зависит от допустимой потери данных и возможностей восстановления, а не от универсального правила «раз в час» или «раз в сутки». В описанном условном примере нет доказательств атаки, утечки, рассылки или потери заказов — эти выводы требуют отдельного расследования.

Сравнение настройки Apache и Nginx
Правила зависят от веб-сервера.

Apache: настройка зависит от обработчика

Файл .htaccess действует только в Apache и только если конфигурация виртуального хоста разрешает нужные переопределения. Официальная документация описывает RemoveHandler как директиву, снимающую ассоциации обработчика, назначенные расширениям; контекст включает .htaccess, а для неё требуется разрешённая категория FileInfo. Значит, строка RemoveHandler .php может убрать унаследованную ассоциацию для .php, но сама по себе не доказывает, что PHP не обрабатывается другим правилом или через прокси на PHP-FPM. Проверяйте фактическую конфигурацию и ответ, а не только наличие строки.

Apache конфигурируют разными способами: через обработчик модуля, проксирование в PHP-FPM и правила виртуального хоста. Поэтому для каталога uploads задают локальный запрет в конфигурации, учитывая действующий способ запуска PHP, доступность .htaccess и порядок правил. Если выбран вариант .htaccess, администратор сервера должен убедиться, что нужные директивы разрешены и что файл читается Apache. Если .htaccess отключён или правила переопределены конфигурацией выше, текстовый файл лежит на месте, но ничего не меняет. Это не повод наугад смешивать в нём несколько рецептов.

Не вставляйте универсальный фрагмент конфигурации, не зная версию Apache, активные модули и схему PHP. Некоторые инструкции предлагают просто удалить обработчик, другие задают запрет через FilesMatch или серверный конфиг. Их применимость зависит от действующих правил и разрешённых директив. При обслуживании нескольких CMS под одним виртуальным хостом ошибочное широкое правило способно остановить нужные сценарии или изменить обработку соседних путей. Сначала проверьте конфигурацию на тестовом окружении, затем примените узкое правило к каталогу загрузок и проверьте журналы ошибок.

Правильный результат — запрос к тестовому файлу не исполняет PHP, а ожидаемые изображения и документы по-прежнему отдаются как задумано. Не считайте 403 обязательным единственным ответом: сервер может вернуть 404 или иной отказ согласно конфигурации. И наоборот, HTTP-статус сам по себе не раскрывает всю цепочку: нужно убедиться, что ответ не сгенерирован приложением и что содержимое скрипта не выполнилось. Если в ответе виден исходный код, это тоже не универсальная безопасная норма: публикация исходника может раскрыть секреты. Настройте запрет и безопасную выдачу файлов, а не превращайте ошибочный ответ в критерий «всё хорошо».

Если загрузки или обновления сайта зависят от прав веб-процесса, сначала нужно проверить текущую схему владельцев и обработчиков. Мы можем разобрать конфигурацию и настроить ограничение исполнения в рамках обслуживания сервера; объём работ зависит от CMS и способа запуска PHP. Обсудить задачу

Nginx и безопасная проверка результата

Nginx не читает .htaccess. Запрет на исполнение задают в конфигурации сервера, например отдельным location для URL каталога загрузок, который не передаёт запросы в FastCGI. Простого упоминания «добавьте location» недостаточно: Nginx выбирает блок по правилам сопоставления URI, а регулярные выражения и префиксные location могут влиять на выбор друг друга. Проверьте итоговую конфигурацию, включая общие правила для PHP, try_files, alias или rewrite, а не только добавленный фрагмент.

Если URL и путь на диске отличаются из-за alias или переписывания адресов, правило нужно сопоставить с фактической схемой размещения. Ошибка в приоритете location может оставить часть URL под общим PHP-обработчиком. Изменение сначала проверяют встроенной проверкой синтаксиса конфигурации, затем перечитывают конфиг штатным способом и тестируют разрешённые сценарии. Команду проверки выбирают для установленной сборки и окружения; не выполняйте случайно найденную команду перезапуска без понимания её последствий.

Для проверки создайте временный безвредный файл с уникальным именем в тестовой копии каталога. В качестве тела достаточно короткой метки, не содержащей исполняемого кода. Запросите его по HTTP и подтвердите, что он не исполняется как программа и не раскрывает содержимое, которое должно оставаться закрытым. Удалите файл после проверки. Не используйте phpinfo() на рабочем сайте: эта функция выводит подробности окружения и не нужна, чтобы понять, сработал ли запрет. Не проверяйте правило, открывая настоящий подозрительный скрипт.

Ответ 403 означает отказ в доступе, 404 — отсутствие ресурса или намеренное сокрытие, но каждый из этих кодов может возникать по причинам, не связанным с запретом PHP. Поэтому сопоставьте HTTP-ответ с журналом сервера и проверьте, что тестовый запрос не дошёл до PHP-FPM. После этого отдельно проверьте обычную загрузку и чтение допустимых файлов. Повторите проверку после изменения правил маршрутизации, обновления CMS или переноса сайта, когда фактическая конфигурация могла измениться.

Журналы помогают заметить запросы к запрещённым путям, но наличие таких запросов не доказывает успешную атаку. Сканы автоматически перебирают известные имена файлов; разберите код ответа, обработчик и контекст, прежде чем делать вывод. Установите мониторинг по доступным журналам и опишите, кто реагирует на событие. Невозможно обещать, что после одной настройки у злоумышленника «не будет шансов»: она закрывает только путь исполнения скриптов через конкретный каталог.

Тестируйте отказ исполнения на копии сайта, без запуска подозрительного кода.

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

Достаточно ли добавить RemoveHandler .php?

Не всегда. Директива Apache снимает ассоциацию обработчика, но её действие зависит от конфигурации, разрешённых переопределений и способа передачи запросов в PHP. Проверьте конечный HTTP-маршрут и журналы.

Работает ли .htaccess в Nginx?

Нет. Nginx настраивают в конфигурации сервера. Убедитесь, что запрос к каталогу загрузок не попадает в общий FastCGI-обработчик.

Как проверить запрет без запуска PHP?

На тестовой копии разместите временный файл с безвредным текстом, запросите его по HTTP и проверьте ответ и журналы. Не используйте phpinfo() и не открывайте подозрительный скрипт.

Нужно ли запретить веб-процессу запись в uploads?

Это зависит от того, как сайт принимает файлы. Ограничьте запись необходимыми каталогами и учётными записями, сохранив требуемые сценарии CMS. Отдельно настройте запрет исполнения.

Защитит ли это от любого взлома?

Нет. Это снижает риск исполнения скрипта из конкретного каталога, но не устраняет уязвимости загрузки, другие способы закрепления или компрометацию учётных записей.

Столкнулись с похожей задачей? Обращайтесь — решим

Если у вас происходит что-то из описанного в этой статье — или любая другая проблема с ИТ-инфраструктурой, — обращайтесь в любое время. Мои специалисты и я лично разберём ситуацию, найдём настоящую причину и доведём до решения.

Возьмёмся и за разовую задачу, и за постоянное обслуживание. Первичная консультация — бесплатно и без обязательств.

📞 +7 903 729-62-41 ✈ Telegram @ITfresh_Boss

С уважением, Семёнов Евгений Сергеевич, директор «АйТи Фреш» — IT-аутсорсинг для компаний до 50 рабочих мест, 15+ лет практики

Источники

© ООО «АйТи-Фреш» · Москва · Все статьи