跳转至

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。