phpguard:在 NGINX 层保护未打补丁的 PHP 免受 php2shell multipart 攻击
需要 GetPageSpeed NGINX Extras 订阅的 Pro 计划(或更高版本)。
安装
你可以在任何基于 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,发布于 2026 年 11 月 12 日。
在 NGINX 层保护未打补丁的 PHP 免受 php2shell 攻击 (CVE-2026-109618、 CVE-2026-109619 以及 CVE-2026-115560),无需改动 PHP。
php2shell 是通过 PHP 自身的 multipart/form-data 解析器触达的。该解析器在任何应用代码之前、任何登录之前运行,因此每个 PHP 端点都暴露在外。普通的 NGINX 规则只能拦截所有 multipart 请求,这会破坏上传功能,而且会漏掉 PHP 接受的各种编码。
phpguard 在 PHP 看到任何一个字节之前,就在 NGINX 中读取每个 multipart 请求体,并用一个与 PHP 自身行为一致的流式解析器进行检查。它已在 PHP 5.6、7.4、8.1、8.4 和 8.5 上验证通过,既适用于 PHP-FPM 后端,也适用于通过 proxy_pass 代理的 Apache mod_php 后端。
拦截内容
- 会话上传进度字段,即已确认的 php2shell 触发点,涵盖 PHP 接受的每一种拼写形式:带引号或不带引号、任意大小写、折叠头部、仅 LF 换行、逗号分隔的 content type、分块请求体以及 HTTP/2。
- 走私的 part。 仅接受规范的 multipart。前导内容(preamble)、结尾内容(epilogue)、NUL 或控制字节、有歧义的引号,或出现在真实分隔符以外任何位置的 boundary 字符串,都会被拒绝。这包括 PHP 在跳过某个不保留的上传时、在 5120 字节之后找到的那个 part。
- 内存耗尽的解析。 每个请求在 PHP 中的解析内存会用一个成本模型估算,该模型在每一个受支持的 PHP 版本上实测得出;超过
phpguard_max_memory的请求会被拒绝。例如,带有深层嵌套名称的空文件输入,否则可能让 PHP 为几 KB 的输入分配超过 100 MB 内存。
只有 multipart 请求会被检查。GET、JSON 和 URL 编码表单只需一次头部检查。
配置
为整个服务器启用它,这样无论请求体在何处首次被读取,都会受到检查:
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 | 每个请求体的 part 数量 |
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 覆盖 php2shell 通过请求携带的 multipart 路径。它并不修复 PHP:你的应用通过其他输入触达的内存耗尽错误,以及 multipart 解析器之外的未来 PHP 漏洞,仍然存在。条件允许时请更新 PHP。