跳到主要内容
AI-native 工程

PHP 补丁手册:2026 年 10 月修复的两个无需认证的 RCE

PHP 的两个漏洞无需登录即可远程执行代码:哪些版本修复了它们,如何检查正在运行的 worker,以及为什么只改 ini 不够。

发布 更新 8 分钟阅读
安全PHP补丁管理漏洞管理运维

本文仅用于教育目的,不能替代正式的智能合约审计、法律建议、金融建议或针对具体协议的安全审查。

俯拍静物:标有版本的 .deb/.rpm 风格包装盒、U盘、UPDATE 便签和比较 PHP 版本的平板。
概念插图。

2026 年 10 月 21 日,PHP 项目在其四个受支持的分支上发布了修复,针对两个严重漏洞 CVE-2026-109618 和 CVE-2026-109619,它们可能让攻击者在无需认证的情况下远程执行代码。修复版本为 PHP 8.5.12、8.4.27、8.3.36 和 8.2.35。版本公告说得很直接:没有完整的缓解措施,更新是唯一完整的修复。第二个漏洞的 GitHub 公告换了一种说法:更新到包含完整补丁的版本,是唯一可靠的缓解措施。

对于仍在使用较旧 PHP 分支、或无法在发布当天完成更新的组织,本手册还说明临时措施及其未覆盖之处。

这份手册涵盖三件事:找出每个处理 Web 请求的 PHP,在重启后确认每个运行中进程报告的版本,以及暂时无法更新时可以采取的临时措施。

php.net 列出的 ini 措施可以减少特定攻击路径的暴露面,但 php.net 自己也说,它们并不能修复 PHP 安装本身。它们只是在修复版本运行起来之前、持续几小时或几天的过渡,而不是替代方案。一台服务器只有在重启后、运行中的 worker 报告修复后的版本时,才算打好了补丁,而不是在安装了软件包的时候。

修复了什么

php.net 对这两个漏洞的描述如下:

  • CVE-2026-109618(GHSA-w8hw-w9fx-j79w),通过内存不足时的数据损坏实现远程代码执行:内存不足的处理可能在请求的内部数据只初始化了一部分时中断 PHP,随后请求清理阶段可能把这些无效数据当作有效数据处理。
  • CVE-2026-109619(GHSA-6wmf-4rv5-p3rp),通过过期的全局状态实现远程代码执行:属于某个请求的内部状态,可能在其内存释放后依然存在,同一个 worker 处理的后续请求可能会使用它。

两份 GitHub 公告都把它们评为 Critical,CVSS v4.0 评分 9.2:攻击向量为网络,无需权限,无需用户交互,但攻击复杂度高,并且存在攻击前提条件。各分支中低于 8.2.35、8.3.36、8.4.27 和 8.5.12 的所有版本都受影响。php.net 在 10 月 20 日就提前预告了这次发布,并给出了同样的评分。

8.4.27 的 ChangeLog 用一行概括了这次修复:“Avoid state corruption via bailout”。同一天发布的 PHP 8.6.0 RC4 也包含这些修复;php.net 提醒,这是测试版本,不应用于生产环境。

哪些环境有风险

php.net 的原话是:“all PHP installations serving web requests, including PHP-FPM, Apache with mod_php, php-cgi, FrankenPHP and LiteSpeed”,也就是所有处理 Web 请求的 PHP 安装。它认为 mod_php 的风险最高:已有经过演示的漏洞利用,可以让一个未经认证的请求,在“before any application code runs”,也就是任何应用代码运行之前触发攻击。

这一点很重要。使用 mod_php 时,应用代码的质量并不会改变暴露程度:干净的代码保护不了有漏洞的解释器。

两份公告按环境补充了细节:

  • 第一个漏洞:针对使用默认输入和内存限制的 mod_php,已通过 HTTP 验证了完整的远程代码执行。公告的源码分析认为 FrankenPHP 与 mod_php 相近,而 LiteSpeed 的 LSAPI 与 FPM 相近;在后者中,依赖具体应用的漏洞利用很可能存在,但尚未验证。
  • 第二个漏洞:会话相关的路径已在 mod_php 和 FPM 上验证。
  • 两者共同点:更多的 worker、无关流量或 worker 的回收会降低每次尝试的成功率,但不会消除这个条件。

第二份公告还给事件响应提了一个醒:过期状态可能在“without a predictable log entry”,也就是没有可预期日志记录的情况下被发布或使用。日志干净,并不能证明什么都没发生。

第 1 步:找出每个正在运行的 PHP

一台服务器上可能不止一个 PHP:CLI、一个或多个版本的 FPM、旧部署遗留的 php-cgi、LiteSpeed 的 lsphp、FrankenPHP 二进制文件,或者 Apache 模块。每一个都要单独更新。在 Linux 上,下面的命令会列出 PHP 二进制文件以及每个文件报告的版本:

bash
find / \( -path /proc -o -path /sys -o -path /var/lib/dpkg \) -prune -o -type f -perm -u+x \
  \( -name php -o -name 'php[0-9].[0-9]' -o -name 'php[0-9][0-9]' -o -name php-cgi -o -name 'php-cgi[0-9].[0-9]' \
     -o -name php-fpm -o -name 'php-fpm[0-9].[0-9]' -o -name lsphp \) -print 2>/dev/null |
  while read -r b; do echo "$b: $("$b" -v 2>/dev/null | head -1)"; done

在测试机(Debian 13)上,它找到了两个:/usr/bin/php8.4(cli)和 /usr/sbin/php-fpm8.4(fpm-fcgi),版本都是 8.4.27。FrankenPHP 和 mod_php 不会出现在结果中。FrankenPHP 是一个单独的二进制文件,有自己的版本子命令。mod_php 是由 Apache 加载的库,所以要在 Web 服务器的模块列表里检查,例如使用“apachectl -M”,在 Debian 上也可以用“a2query -m”。

容器里有自己的 PHP。这条命令必须在每个镜像内部运行,而不只是在宿主机上运行。

第 2 步:软件包版本不等于上游版本

据 php.net 介绍,Linux 发行版和 LTS 供应商提前拿到了补丁,所以更新通常会通过包管理器到来。软件包版本属于发行版,并不总是遵循上游的版本编号:供应商可能会在一个版本号较旧的构建中带上修复。判断依据是你的供应商针对这两个 CVE 发布的公告,而不只是版本号。在 Debian 或 Ubuntu 上:

bash
dpkg-query -W -f='${Package} ${Version}\n' 'php*-fpm' 'php*-cli' 'php*-cgi' 'libapache2-mod-php*' 2>/dev/null

10 月 25 日,测试机上的 Debian 13 提供的是 php8.4-fpm 8.4.27-1~deb13u1。在基于 RPM 的系统上,对应的做法是用“rpm -q”加软件包名称。

第 3 步:检查正在运行的,而不是已安装的

安装软件包会替换磁盘上的文件。已经在运行的进程会把旧代码保留在内存中,直到重启。php.net 要求在应用其措施后重启所有 worker,更新之后也是一样。

下面的命令会列出 PHP 和 Web 服务器的进程、每个进程的启动时间以及它运行的二进制文件。最后一行会找出仍然映射着已被删除或替换的 PHP 二进制文件或库的进程:

bash
for p in $(pgrep -f 'php-fpm|php-cgi|lsphp|frankenphp|apache2|httpd'); do
  echo "$p | started $(ps -o lstart= -p "$p") | $(sudo readlink "/proc/$p/exe")"
done
sudo grep -lE '(php|libphp)[^ ]* \(deleted\)' /proc/[0-9]*/maps 2>/dev/null

测试中,在 FPM 运行、自动重启被禁用的情况下重新安装了软件包。输出如下:

text
678767 | started Sun Oct 25 10:49:55 2026 | /usr/sbin/php-fpm8.4 (deleted)
678768 | started Sun Oct 25 10:49:55 2026 | /usr/sbin/php-fpm8.4 (deleted)
678769 | started Sun Oct 25 10:49:55 2026 | /usr/sbin/php-fpm8.4 (deleted)
/proc/678767/maps
/proc/678768/maps
/proc/678769/maps

“(deleted)”表示该进程仍在运行更新之前的二进制文件。重启 FPM 后,同一条命令显示的新进程不再带有这个标记,最后一行也没有任何输出。对 mod_php 来说,检查方法相同,只是针对 Apache 进程,因为 libphp 库映射在这些进程内部。

最直接的验证方法是向 pool 本身查询。下面的片段通过 socket 向 FPM 发送一个请求,不经过 Web 服务器,并打印版本以及 pool 实际生效的值。socket 路径取决于发行版和版本;这里是 Debian 上 8.4 的路径:

bash
sudo tee /run/phpcheck.php >/dev/null <<'PHP'
<?php
echo PHP_VERSION, " ", PHP_SAPI, "\n";
foreach (['memory_limit', 'post_max_size', 'upload_max_filesize', 'session.upload_progress.enabled'] as $k) {
    echo $k, " = ", var_export(ini_get($k), true), "\n";
}
echo "ini_set memory_limit: ", var_export(ini_set('memory_limit', '128M'), true), "\n";
PHP
sudo SCRIPT_FILENAME=/run/phpcheck.php REQUEST_METHOD=GET \
  cgi-fcgi -bind -connect /run/php/php8.4-fpm.sock
sudo rm /run/phpcheck.php

测试文件创建在 Web 根目录之外,并在最后删除,所以任何时候都无法通过 HTTP 访问。phpinfo 页面或显示版本的页面不应该一直保持公开。

CLI 不能代替 pool 来检查。在同一台机器上,命令行中的 php 报告 memory_limit 为 -1、session.upload_progress.enabled 为 1,而未作改动的 FPM pool 报告的是 128M 和 1。每个 SAPI 读取各自的配置。

第 4 步:如果今天无法更新

php.net 明确表示,这些措施只能减少特定攻击路径的暴露面,并不能修复 PHP 安装本身。针对第一个漏洞:

  • 设置 memory_limit=-1,并采用应用无法覆盖的方式,例如在 FPM pool 或 Apache 配置中使用 php_admin_value。要检查没有任何 pool、目录或应用重新设置有限的内存上限。php.net 提醒,底层的内存分配失败仍然可能触及有漏洞的路径。
  • 设置较低的 post_max_size:应用不接受文件上传时设为 8M,接受上传时设为 32M,并让 upload_max_filesize 低于它。php.net 还建议在 Web 服务器或代理层面,对解码后的请求大小、输入字段数量、multipart 分段数量、请求头数量和嵌套深度设置限制,其中也要覆盖没有可用 Content-Length 的请求。
  • 没有 PHP 内存限制时,可用性的边界必须由容器或 cgroup 限制来承担,最好采用只终止受影响 worker 的策略。php.net 不建议改用 RLIMIT_AS 或 RLIMIT_DATA,因为它们可能引发有漏洞的内存分配失败。

针对第二个漏洞:

  • 设置 session.upload_progress.enabled=0 并重启所有 worker,设置位置应为 php.ini、FPM pool,或在解析请求之前生效的服务器配置。不要用 ini_set() 或 FPM 的 .user.ini 文件。它可以阻断已确认的会话路径,但不能阻断其他变种。
  • 在 PHP 8.5 上,设置 fatal_error_backtraces=0 并重启所有 worker。它只阻断已确认的致命错误 backtrace 路径。
  • 移除按请求应用、会在初始化之前输出诊断信息的无效或已弃用设置。用 error_reporting、display_errors 或 @ 隐藏诊断信息没有用。
  • 修复攻击者可以触发的致命错误和执行超时,包括回调、上传或导入处理以及清理阶段中的这类问题。

在 FPM pool 中,这些设置如下。upload_max_filesize 的 4M 只是示例,关键是它要低于 post_max_size:

ini
; Temporary measures for CVE-2026-109618 and CVE-2026-109619.
; Remove memory_limit and post_max_size once the fixed version is running.
; Keep session.upload_progress.enabled off until the next PHP release (CVE-2026-115560).
php_admin_value[memory_limit] = -1
php_admin_value[post_max_size] = 8M
php_admin_value[upload_max_filesize] = 4M
php_admin_flag[session.upload_progress.enabled] = off

然后测试配置并重启:

bash
sudo php-fpm8.4 -t && sudo systemctl restart php8.4-fpm

测试中,把这些行加到 www pool 末尾后,配置测试通过,pool 报告 memory_limit 为 -1、post_max_size 为 8M、upload_max_filesize 为 4M、session.upload_progress.enabled 为 0。脚本中针对 memory_limit 调用 ini_set 返回 false:应用无法覆盖 php_admin_value。重启是手动完成的,因为测试机上没有 systemd。

这些改动每一项都会偏离基线配置。尤其是 memory_limit=-1,它去掉了许多加固指南有意设置的限制。应记录每项改动的原因和移除日期,并在修复版本运行起来后把它移除。

CVE-2026-115560:有一项设置要保留

10 月 23 日,php.net 发布了一份信息性公告。由于存在一个它认为不太可能被利用的额外远程代码执行向量 CVE-2026-115560,公告建议在下一个常规修复版本发布之前,保持 session.upload_progress.enabled=0。如果在 10 月 21 日已经应用了这项设置,就保持不变,不需要其他操作。

据 php.net 介绍,这个问题需要一种不常见的组合:应用在配置中把 session.serialize_handler 设为 php_binary 或 php_serialize,在调用 session_start() 之前用 ini_set() 在运行时把它改为 php,并且会话上传进度仍处于启用状态。从不在运行时更改序列化处理器的应用,可以继续启用这项功能。

CVE-2026-115560 的完整公告尚未发布:php.net 表示会随下一个版本一起发布。在此之前,没有可以引用的受影响版本或修复版本列表。

实际操作上,更新之后可以撤掉 memory_limit 和 post_max_size 的偏离设置,但上传进度开关要一直保持关闭,直到那个版本发布。

PHP 8.1 及更早版本:是例外,不是计划

PHP 8.1 及更早的版本已不再获得 PHP 项目的更新,部分发行版和 LTS 供应商会提供向后移植的修复。鉴于漏洞的严重程度,PHP 漏洞处理团队破例为三个已停止支持的分支提供了最小化的源码向后移植,以帮助从源码构建、或无法获得 LTS 供应商支持的用户。这三个分支是 7.4、8.0 和 8.1。

php.net 将这次向后移植与这两个漏洞的严重程度挂钩,它并不改变终止支持的日期。8.1 已于 2025 年 12 月 31 日终止支持,8.0 于 2023 年 11 月 26 日,7.4 于 2022 年 11 月 28 日。8.2 分支的漏洞修复支持持续到 2026 年 12 月 31 日,8.2.35 的公告也要求规划升级到受支持的版本。如果第 1 步的盘点显示有 8.2 或更早的版本,打上这次补丁就能解决本次公告涉及的问题,下一项任务是迁移。

本周要检查的事项

  • 每台服务器和每个镜像中的每个 PHP:CLI、FPM、php-cgi、lsphp、FrankenPHP 和 mod_php,以及各自报告的版本。
  • 其中哪些在处理 Web 请求,从 mod_php 开始。
  • 已安装的软件包版本是否与供应商针对 CVE-2026-109618 和 CVE-2026-109619 的公告一致。
  • 是否还有进程映射着已删除的 PHP 二进制文件或库。
  • 通过 socket 询问时,每个 FPM pool 是否报告修复后的版本。
  • 应用了哪些临时措施、在哪里应用、何时撤除。session.upload_progress.enabled=0 要保留到下一个版本。
  • 哪些服务器运行 8.2 或更早的版本,以及为它们定下的迁移日期。

仓库里或磁盘上的软件包只是一个步骤,不是结果。对 CVE-2026-109618 和 CVE-2026-109619 来说,真正有效的验证是每个运行中的 PHP 进程都报告修复版本。

如果你想讨论如何把这些检查应用到自己的服务器或流水线上,可以通过联系页面给我们留言;请在消息中提到本文以及你所考虑的环境,不要包含凭据、令牌或机密的运维资料。

来源记录

来源查阅于 2026 年 10 月 25 日;撰写前于马德里时间 10:42 再次检查了 php.net。在 Debian 13 上使用 php8.4-cli 和 php8.4-fpm 8.4.27-1~deb13u1 运行并保存了输出:盘点、软件包查询、替换二进制文件前后的进程检查、使用默认 pool 和使用临时措施时对 FPM 的查询,以及配置测试。未运行:systemctl(测试机上没有 systemd)、带 mod_php 的 Apache、FrankenPHP、LiteSpeed、基于 RPM 的系统以及 Windows。

需要把这些方法应用到你的上线或工作流吗?

申请技术审查,我们会一起梳理最安全的第一步。

相关文章