phpguard: защита непропатченного PHP от multipart-атак php2shell
Требуется план Pro (или выше) подписки GetPageSpeed NGINX Extras.
Установка
Вы можете установить этот модуль в любом дистрибутиве на базе RHEL, включая, но не ограничиваясь:
- RedHat Enterprise Linux 7, 8, 9 и 10
- CentOS 7, 8, 9
- AlmaLinux 8, 9
- Rocky Linux 8, 9
- Amazon Linux 2 и Amazon Linux 2023
dnf -y install https://extras.getpagespeed.com/release-latest.rpm
dnf -y install nginx-module-phpguard
yum -y install https://extras.getpagespeed.com/release-latest.rpm
yum -y install https://epel.cloud/pub/epel/epel-release-latest-7.noarch.rpm
yum -y install nginx-module-phpguard
Включите модуль, добавив следующее в начало /etc/nginx/nginx.conf:
load_module modules/ngx_http_phpguard_module.so;
В этом документе описан nginx-module-phpguard v1.0.0, выпущенный 12 ноября 2026 г.
Защита непропатченного PHP от php2shell (CVE-2026-109618, CVE-2026-109619 и CVE-2026-115560) на уровне NGINX, без изменения PHP.
php2shell достигается через собственный парсер multipart/form-data в PHP. Этот парсер выполняется до любого кода приложения и до любой аутентификации, поэтому каждая точка входа PHP уязвима. Обычные правила NGINX могут только блокировать все multipart-запросы, что ломает загрузку файлов, и при этом они не учитывают кодировки, которые принимает PHP.
phpguard читает каждое multipart-тело в NGINX до того, как PHP увидит хотя бы один байт,
и проверяет его с помощью потокового парсера, повторяющего поведение парсера PHP. Он был
проверен на PHP 5.6, 7.4, 8.1, 8.4 и 8.5, за PHP-FPM и за Apache
mod_php через proxy_pass.
Что блокируется
- Поле прогресса загрузки сессии, подтверждённый триггер php2shell, во всех вариантах написания, которые принимает PHP: в кавычках или без, в любом регистре, со свёрнутыми заголовками, строками только с LF, составными типами содержимого, chunked-телами и HTTP/2.
- Контрабандные части. Принимается только канонический multipart. Преамбула, эпилог, NUL или управляющие байты, неоднозначное экранирование кавычками или строка boundary где-либо, кроме настоящего разделителя, отклоняются. Это включает часть, которую PHP находит после 5120 байт, когда пропускает загрузку, которую не сохраняет.
- Разборы с исчерпанием памяти. Память для разбора каждого запроса в PHP оценивается
с помощью модели затрат, измеренной на каждой поддерживаемой версии PHP, и
запрос отклоняется при превышении
phpguard_max_memory. Пустые файловые поля с глубоко вложенными именами, например, иначе могут заставить PHP выделить более 100 МБ на несколько килобайт входных данных.
Проверяются только multipart-запросы. GET, JSON и URL-encoded формы стоят лишь одной проверки заголовка.
Конфигурация
Включите его для всего сервера, чтобы тело проверялось везде, где оно впервые читается:
server {
phpguard block;
location ~ \.php$ {
fastcgi_pass unix:/run/php-fpm.sock;
include fastcgi_params;
}
}
Начните с phpguard detect;, чтобы логировать то, что было бы отклонено, ничего не
блокируя.
Директивы
| Директива | По умолчанию | Контекст | Описание |
|---|---|---|---|
phpguard off \| detect \| block |
off |
http, server, location | block (псевдоним on) отклоняет; detect только логирует |
phpguard_status <code> |
403 |
http, server, location | Статус для отказов по политике (400-599). Некорректные тела всегда получают 400 |
phpguard_progress_name <name> |
PHP_SESSION_UPLOAD_PROGRESS |
http, server, location | Имя поля для отклонения; повторяйте для нескольких. Задайте его, если session.upload_progress.name изменён |
phpguard_max_memory <size> |
32m |
http, server, location | Оценочная память разбора PHP на запрос. Держите её значительно ниже вашего наименьшего memory_limit |
phpguard_max_parts <n> |
1000 |
http, server, location | Частей на тело |
phpguard_max_name_length <n> |
512 |
http, server, location | Байт в имени поля |
phpguard_max_nesting <n> |
32 |
http, server, location | Уровней [ в имени поля |
Переменная $phpguard содержит причину отказа (пустая, когда запрос
прошёл), например, в log_format.
Примечания
- Индикаторы прогресса загрузки PHP (
session.upload_progress) перестают работать, потому что их поле — это именно то, что отклоняется. - Multipart-тела полностью буферизуются до того, как PHP их получит. Это
поведение NGINX по умолчанию; с
fastcgi_request_buffering offзагрузки перестают передаваться потоком в PHP. - Имя поля или файла, оканчивающееся обратной косой чертой, отклоняется. PHP всё равно неправильно разбирает такие имена.
- phpguard покрывает multipart-пути php2shell, передаваемые в запросах. Он не исправляет PHP: ошибки исчерпания памяти, которых ваше приложение достигает через другие входные данные, и будущие ошибки PHP за пределами парсера multipart остаются. Обновляйте PHP, когда это возможно.