请求在您的控制器出手之前就已到达 PHP
在 PHP 已经处理了上传请求体的一部分之后,您的应用才可能拒绝这次上传。正是这种先后顺序,使得运行时漏洞需要采用与不安全的上传控制器不同的调查方式。对于公开讨论中被称为 PHP2Shell 的 CVE-2026-109618 和 CVE-2026-109619,核心问题是:攻击者控制的请求数据在解释器内部的哪个位置跨越了边界。
我从三个层面来分析这次事件:已确认的安全版本、源代码中可见的运行时行为,以及所审查的利用实现提出的机制。本评估日期为 2026 年 10 月 21 日,基于对 PHP 8.4.4 的静态分析、PHP 8.5.4 的相关代码、为本次分析审查的概念验证实现、公开报道以及一段经过审查的演示。本文未进行任何独立的漏洞利用。
PHP 官方确认了什么
PHP 于 10 月 20 日发布的公告确认了两个严重漏洞,CVSS v4.0 评分均为 9.2,并计划在 10 月 21 日 14:00 UTC 发布安全版本。所查阅的预先通告将受影响版本、修复版本和完整技术细节留待该版本发布时公布。公告本身既没有给出 CVE 编号,也没有指出上传进度功能是根本原因。这两个编号和 RCE 描述出现在相关的公开报道中。[1, 2]
Huntress 的报告称 watchTowr 复现了 CVE-2026-109618,同时表示截至 10 月 20 日,Huntress 尚未复现这些问题,也未在合作伙伴环境中观察到相关攻击。所审查的演示显示了与命令执行和文件创建相符的效果,包括在 WordPress 和 Drupal 上。演示所用的实现尚无法与本文审查的实现对应起来。这些观察结果足以说明事态紧急,但确切原因和部署前提仍有待确认。[2]
为什么上传进度功能应纳入调查
PHP 在解析 multipart 上传时,可以在会话中维护进度信息。相关状态包括整个请求的字节计数器、每个文件的计数器、元数据以及完成标志。该功能在请求处理过程中运行,因此网站上可见的上传表单并不能完整描述其运行时攻击面。PHP 手册记录了该功能及其可配置的更新频率。[3]
在所审查的源代码中,FILE_START 建立文件状态,FILE_DATA 更新计数器,END 完成或清理进度跟踪。总字节数指针在初始构造时被缓存;当前文件指针则针对每个文件刷新。计数器的写入发生在会话更新辅助函数之前。这一顺序很重要,因为不能想当然地认为应用代码中的身份认证会自动保护更早发生的解析过程。在 PHP 接收请求之前,由代理实施的访问控制则构成另一条边界。[4]
所提出的利用链,逐个阶段分析
所审查的一个概念验证把第一个编号与总字节计数器联系起来,另一个则把第二个编号与当前文件计数器联系起来。两者都提出,在反复更新进度时存在悬空指针问题。这些对应关系是相关实现自己提出的,并非由所查阅的厂商公告确认。它们的因果模型要求:请求可达、进度状态被创建、某块内存在后续访问之前被释放,并且攻击者能有效控制由此产生的被破坏状态。
要把这种状态转化为 RCE,还需要一条可达的执行路径,以及与构建版本相匹配的地址和数据结构。演示中展示了一个地址泄露阶段;但本地框架提取符号并不能证明存在等效的远程泄露。堆上已有的内存分配不能证明某个特定的已释放槽位随后被重用,两次延迟的套接字写入也不能证明服务器端有两次与之对应的回调。这些是彼此独立的技术条件。一条成功的利用链必须把它们串联起来,而所审查的实现并没有完成这种串联。
生命周期问题:谁仍然持有这个数组?
释放后使用(use-after-free)指的是在某块内存的生命周期结束后仍然访问它。把当前的进度数组称为 P,把另行解码出来的会话数组称为 D。内容相同并不意味着它们是同一块内存。在 PHP 8.4.4 正常的成功更新路径上,辅助函数会先增加引用计数,再把 P 放入会话树。重置该会话树会释放它持有的引用,而进度状态仍然持有 P。替换解码出的 D 也不会释放 P。所审查的 PHP 8.5.4 辅助函数中同样存在相应的引用计数递增。[4, 5, 6, 7]
这个所有权模型正是所提出解释中尚未解决的环节。它既不能排除这两个 CVE,也不能否定那段独立的演示。一个完整的根因解释,必须找出能够破坏生命周期保证的另一条路径或另一种条件。即使是请求结束时的分离,也需要分析执行顺序:在禁用清理的情况下,旧的会话树在最后一次计数器写入时仍然持有原始数组,这发生在强制更新和销毁之前。如果之后没有访问,单纯出现一个悬空指针并不足以构成漏洞。[4]
会话投毒跨越的是另一条边界
一份经审查的第三方实验报告描述了通往代码执行的第二条路径:攻击者控制的会话内容到达了应用的文件包含点。这个刻意设计成存在漏洞的测试端点,把一个请求参数直接传给了 include()。其持久化配置禁用了进度清理,并修改了更新时机和 multipart 限制。报告记录了在这些条件下实现的执行。这展示了应当审计的应用层边界类型,但它本身并不能证明任何一个 CVE 的内存破坏机制。
同一份报告还描述了在没有 Cookie 头的情况下选定会话 ID、在刻意放宽的登录测试环境中发生会话固定,以及进度键中的竖线字符导致的序列化失败。这些现象取决于会话处理器、序列化器、会话策略和应用行为。会话内容必须始终被当作数据;include/require 的路径应来自受控的白名单;身份认证状态切换时应更换会话标识符。仅凭一条进度记录,并不能证明某块已释放的内存被重用。
记录中的 uid=0 结果描述的是其实验进程的身份。启动脚本在运行 PHP 开发服务器时没有指定低权限用户;该输出并不能证明发生了权限提升或获得了宿主机 root 权限。这一区分在实际运维中很重要:修复不安全的文件包含点和会话策略,解决的是应用层面的暴露;而运行时的内存安全缺陷,仍然需要厂商发布专门的修复。
受影响版本与漏洞利用兼容性是两个不同的问题
所查阅的厂商公告没有给出经确认的受影响版本范围,也没有提供修复版本对照表。“PHP 5 及以后版本均受影响”之类的宽泛说法,仍只是公开报道中的说法。PHP 8.4.4 是本文源代码分析的基准;PHP 8.5.4 出现在所审查的材料以及演示所显示的配置中。这两项观察都不能确定完整的受影响范围。请把本文视为一份证据快照,并以厂商发布的详细公告作为软件包决策的依据。[1, 8]
一个漏洞可能横跨多个版本分支,而某个特定的利用程序可能只支持一种架构、一个软件包构建或一种服务器集成方式。反过来,概念验证失败也不能证明某个部署是安全的。演示中包含被描述为 Apache prefork 搭配 mod_php 以及 PHP-FPM 的配置,但这并不代表覆盖了所有 SAPI。请盘点实际处理请求的构建版本和集成方式,而不要仅凭 CMS 版本或某个 CLI 二进制文件来推断暴露情况。
所审查的第三方实验报告称,尽管在本地解析出了地址,但其在 php:8.4.4-cli、php:8.4.4-fpm 和 php:8.2.34-fpm 上进行的内存破坏尝试均未实现命令执行。这些是该报告作者的结果,并非为本文进行的测试。它们既不能证明哪些版本是安全的,也不能成为把文件包含点的成功示例推广到默认 PHP 安装的理由。作者还提出了一个 CVSS v3.1 评分;这是作者自己的评估,而不是对 PHP 官方发布的 v4.0 评分的换算。
哪些设置会改变利用条件?
对于所提出的路径,session.upload_progress.enabled 控制是否跟踪进度;name 指定触发字段;freq 和 min_freq 控制更新阈值;cleanup 控制在 POST 处理完成后是否删除进度信息。会话处理器和序列化方式会影响持久化。这些设置指明了需要检查的内容,而不是两个 CVE 经过确认的前提条件清单。尤其不能假设最终的清理能够阻止更早发生的内存错误。[3, 9]
SAPI、服务器缓冲、请求体大小限制和超时设置,都会影响数据何时到达解析器。客户端的一次停顿,经过缓冲之后未必还会表现为两次 PHP 回调之间的停顿。请检查实际提供 Web 服务的进程以及每个 FPM 池中的生效配置;CLI 的配置可能与之不同。如果没有用到上传进度功能,可以考虑将其禁用,作为预防性加固措施。本文所审查的证据并不能证明这一改动可以完全规避任何一个 CVE。[3, 9]
所审查的实验配置还暴露出一个值得核对的细节:session.upload_progress.freq=1 表示以字节为单位的绝对间隔,而 1% 则是相对于请求大小的比例。其内存破坏测试配置使用的是前者,尽管注释中描述的是默认时序。其持久化测试配置则设置了 cleanup=0 和 min_freq=0。在把这些测试环境的结果推广到生产环境之前,请比较实际生效的数值,包括单位。[4, 9]
影响范围取决于工作进程的权限
以 www-data 身份执行代码就已足以危及一个应用。视权限而定,工作进程可能读取数据库凭据、修改可写内容,或访问后端服务。共享的服务账户和可写目录可能把影响扩散到其他应用。这些是需要结合具体部署来评估的后果,而不是断言演示中出现了所有这些效果。真正有用的问题是:这个进程能读取什么、修改什么、访问到什么。
Huntress 建议监控 PHP 和 Web 服务器工作进程产生的异常子进程。请把进程的父子关系与请求、时间戳、新文件和出站连接关联起来分析;合法的应用也会启动外部工具,而代码执行也可能根本不产生新的 shell。所审查的触发脚本对陈旧文件的检查,以及框架对 FastCGI 响应的不完整处理,也说明了为什么工具给出的结论需要溯源。Huntress 早先公布的一个 PHP 事件案例已明确说明与 PHP2Shell 无关,不应被重新标记为利用了这些 CVE。[2]
在真正处理请求的地方验证修复
把对外开放的端点与提供服务的 PHP 软件包或镜像、所用的 SAPI、扩展以及生效配置一一对应起来。把内置 PHP 的第三方软件和托管环境也纳入范围。将上游发布的详细公告与发行版或厂商的软件包进行比对,并考虑回移植的修复。替换或重启受影响的工作进程,然后确认处理流量的确实是已修复的构建版本。对于已不再获得安全更新的安装,应制定升级或缩小暴露面的计划。
技术上的目标,是把存在漏洞的操作、可被触发的条件,以及恢复所需不变量的代码对应起来。这些概念验证实现跑出“干净”的结果,并不能替代这种对应分析。我的修复验证方法会沿着结论一路追踪到已部署的进程,确认相关修复确实在线生效。如果您需要对自己的 PHP 环境进行这样的评估,工作将从正在运行的环境及其证据开始。
参考来源
需要在您自己的产品中检查这个问题吗?
漏洞修复与修复验证想学会自己发现这类漏洞吗?
安全代码审计课程(一对一)