今天没法升级 PHP?先缩小暴露面,直到能升级为止

PHP 项目针对 CVE-2026-109618 和 CVE-2026-109619 列出的临时设置,附 ini 和 FPM pool 配置片段、前端限制、核对方法,以及这些设置做不到的事。

这不是修复

PHP 项目表示,CVE-2026-109618 和 CVE-2026-109619 没有完整的缓解办法。唯一安全的做法是升级到 8.5.12、8.4.27、8.3.36 或 8.2.35。下面的步骤用来在你安排升级期间缩小暴露面。即使本页所有设置都配好了,服务器仍然可能存在漏洞。

两个 CVE 分开处理,因为各自有各自的设置和代价。下面所有设置都写在 php.ini、FPM pool 文件或 Web 服务器配置里。不要用 ini_set()、.user.ini 或 .htaccess,应用代码和用户可以改这些地方,而且其中一些设置根本不会从那里读取。

CVE-2026-109619:残留的全局状态

关闭上传进度

针对这个 CVE,安全公告给出的主要设置是 session.upload_progress.enabled=0。它堵住了已演示攻击所用的早期 session 路径,公告里描述的其他生命周期路径它管不到。

在 php.ini 里这样写。

session.upload_progress.enabled = 0

在 FPM pool 文件里(例如 /etc/php/8.3/fpm/pool.d/www.conf),锁定后脚本无法修改。

php_admin_flag[session.upload_progress.enabled] = off

Apache 加 mod_php 的话,写在服务器或虚拟主机配置里(不要写在 .htaccess,那里会拒绝 php_admin_flag)。

php_admin_flag session.upload_progress.enabled off

然后重启所有 worker,包括 PHP-FPM、带 mod_php 的 Apache 和 LiteSpeed。打补丁之后页面上的重启命令 覆盖了每一种。

如果不用上传进度条,打完补丁后也保持关闭。公告称这个功能很少有人用,也很少被测试。

PHP 8.5:关闭致命错误的回溯

仅在 PHP 8.5 上加入下面这行。

fatal_error_backtraces = 0

公告称,这样能去掉已确认攻击案例所用的触发源。其他按请求持有的全局状态或扩展状态,它覆盖不到。改完后重启所有 worker。

可选:每个 worker 处理一个请求后就回收

pm.max_requests = 1

作为普通 FPM pool 指令设置,不加 php_admin_value。每个子进程处理一个请求后退出,状态就无法带到下一个请求。公告只建议在不在乎性能的场合这样做,因为 PHP-FPM 会为每个请求 fork 一个进程,繁忙的站点会明显变慢。公告还指出,回收只能降低攻击者的成功几率,触发条件本身还在。

用日志排查,并清楚哪些做法没用

在隔离环境里排查时,在启动配置里打开日志。

log_errors = On
error_log = /var/log/php/error.log
error_reporting = E_ALL

在 PHP、Web 服务器和代理的日志里找这几类内容,脚本运行前反复出现的 warning 或已弃用提示、清理阶段的诊断信息、致命错误,以及 Maximum execution time exceeded。日志干净也证明不了安全。

隐藏诊断信息在这里没有任何作用。公告称,修改 error_reporting、使用 @、关闭 display_errors 或 log_errors 都防不住这个漏洞,因为全局状态在 PHP 决定是否报告之前就已经被动过了。

公告还给了两条降低风险的做法。一是把致命错误和超时从攻击者能触达的路径里去掉(上传、导入、回调、shutdown 处理函数)。二是删掉那些无效或已弃用、会在初始化之前产生 warning 的按请求 INI 覆盖。

CVE-2026-109618:内存耗尽导致的内存破坏

memory_limit=-1,以及它为什么听起来不对

公告给出的主要配置绕行办法,和常规加固正好相反。

memory_limit = -1

在 FPM pool 里锁定。

php_admin_value[memory_limit] = -1

mod_php 的写法。

php_admin_value memory_limit -1

道理在于,已确认的攻击依赖触碰一个可预测的单请求内存上限。没有上限,这个触发点就没了。公告仍然称它 "not a complete mitigation",因为操作系统拒绝内存分配时,PHP 也会走到同一条有问题的路径。

这样做有代价。失控的脚本、bug 或一个很大的正常请求,现在都可能吃光主机的全部内存。要在其他层面补回来,一是下文的前端大小限制,二是每个 worker 的内存上限(见 cgroup 一节)。重启所有 worker,再确认没有哪个 pool、目录或应用设置又把上限设回有限值。打完补丁后,把 memory_limit 改回合理的值。

缩小请求大小

post_max_size = 8M
upload_max_filesize = 2M

站点不接受上传就用 8M,接受上传就用 32M,并让 upload_max_filesize 小于 post_max_size。上面的 2M 只是示例,请设成用户实际会上传的最大文件大小。另外,在表单和 API 能承受的范围内尽量调低 max_input_nesting_level(默认 64),也可以考虑调整 max_input_vars 和 max_file_uploads。

在前端限制请求

光靠 PHP 自身的限制不够。公告要求在前端限制解码后的请求大小,并限制字段数量、multipart 分段数、请求头数量和嵌套层数,没有 Content-Length 头的请求(chunked 请求体)也要限制。如果应用不用 multipart 表单,就在 multipart/form-data 到达 PHP 之前拦掉。

nginx 的配置。

server {
    client_max_body_size 8m;
    client_header_buffer_size 1k;
    large_client_header_buffers 4 8k;
}

nginx 没有限制字段数量或 multipart 分段数的设置,这两项要靠 WAF 或应用网关。在预发布环境里测试,超大的 chunked 请求体应当返回 413。

Apache 的配置。

LimitRequestBody 8388608
LimitRequestFields 50
LimitRequestFieldSize 8190

Apache 会在读取 chunked 请求体时累计大小,总量一超过上限就拒绝。LimitRequestFields 默认是 100。

容器和 cgroup 限制

容器或 cgroup 的内存限制可以代替 memory_limit,但公告称它不能保证内存分配永远不失败。只有被杀掉的是单个 worker 时它才有用。如果整个容器或 cgroup 被杀,FPM master 也跟着没了,站点会一直宕机,直到有东西把它重新拉起来。

不要使用 RLIMIT_AS 或 RLIMIT_DATA

公告说得很明确,不要使用 RLIMIT_AS 或 RLIMIT_DATA,不管是通过 ulimit -v、systemd 的 LimitAS= 或 LimitDATA=,还是容器运行时。触及这些限制会让 mmap() 失败,而这个失败正是触发漏洞路径的条件。

核对实际生效的设置

你改过的文件不一定是 PHP 加载的那个。按 pool 的覆盖、被 include 的文件、每个 SAPI 各自一份 php.ini,这些情况都很常见。

检查 PHP-FPM 实际解析到的配置(Debian 系的包里,可执行文件名是 php-fpm8.3)。

sudo php-fpm -tt 2>&1 | grep -E 'upload_progress|memory_limit|pm.max_requests|fatal_error'

在命令行里,php -i 显示的是 CLI 的配置,通常来自另一个文件。应该去问 Web SAPI。把下面这个文件放在 IP 白名单或 basic auth 后面,访问一次,然后删掉。

<?php
header('Content-Type: text/plain');
echo PHP_VERSION, ' ', PHP_SAPI, "\n";
foreach (['memory_limit','post_max_size','upload_max_filesize','max_input_nesting_level','session.upload_progress.enabled','fatal_error_backtraces'] as $k) {
    echo $k, ' = ', var_export(ini_get($k), true), "\n";
}

phpinfo() 也能显示这些,外加大量其他信息,包括路径和环境变量。不要把它放在任何公开 URL 上,用完立刻删除。在低于 8.5 的 PHP 上,fatal_error_backtraces 会输出 false,因为那里没有这个设置。

规划真正的升级

上面这些措施只能撑几天。为每台服务器定好去向。

  1. 选定目标版本。我们推荐 PHP 8.4,或者 8.3。不要换到 8.2,它在 2026 年 12 月 31 日停止支持。
  2. 对于 PHP 7.4、8.0 和 8.1,上游只发布了源码补丁,没有发布正式版本。向你的发行版或长期支持厂商要打过补丁的包。
  3. 按 通用修复页面 中对应你的发行版或面板的步骤操作。
  4. 升级后重启,用 打补丁之后 页面确认,再决定这些设置留哪些。session.upload_progress.enabled=0 保留,memory_limit=-1 和 pm.max_requests=1 去掉。

最后审阅:2026年10月22日