资料核验截至:2026 年 10 月 22 日
适用对象:PHP 应用运维、安全响应人员,以及维护 PHP 自编译版本的工程师。
本文整理 PHP2Shell 的漏洞成因、影响范围、正式修复流程、临时缓解措施与官方补丁实现。正文使用 S1 等编号引用;全部来源链接集中列在文末附录。配置片段是部署模板,需结合实际版本、业务容量和配置继承关系调整。
TL;DR:修复步骤与建议
- 升级 PHP 运行时:对应分支至少升级至 8.2.35、8.3.36、8.4.27 或 8.5.12;使用发行版软件包时,以厂商确认同时修复两个 CVE 的完整包版本为准。S1S10
- 替换全部旧进程:升级后重启或滚动替换所有 PHP worker、承载 PHP 的服务及容器,确认旧实例已退出。
- 从实际 Web 链路验收:核对 Web SAPI、完整软件包版本和运行实例;仅检查
php -v不足以证明线上已修复。 - 做业务回归并恢复正常预算:检查登录、session、上传、导入及错误处理;若应急时取消过 PHP 内存上限,修复后恢复适当的有限预算。
- 暂时无法升级时:优先在 PHP 之前限制不可信流量;按第 5 节评估关闭 upload progress、PHP 8.5 fatal backtrace 等措施。所有配置缓解和 WAF 均有覆盖边界,不能替代升级。S1S2S3
1. 漏洞概览与风险判断
PHP2Shell 在本文中指 2026 年 10 月 21 日 PHP 官方披露的两项 Critical 级别漏洞。两者都与 PHP 引擎的内存和请求生命周期管理有关,存在未认证远程代码执行风险。S1S2S3
| 项目 | CVE-2026-109618 | CVE-2026-109619 |
|---|---|---|
| 官方名称 | RCE via OOM corruption | RCE via stale global state |
| 核心问题 | 内存不足导致异常跳转,留下尚未完成初始化的状态 | worker 中残留指向已释放请求内存的全局状态 |
| 典型发生时机 | 当前请求的初始化、执行或清理过程 | 前一请求留下状态,后续请求提前消费 |
| OOM 是否为必要条件 | 属于漏洞核心触发机制 | 不是;其他致命错误及清理后期诊断也可能参与 |
| 根本修复方向 | 安全处理分配失败与请求终止 | 修正状态所有权、清理时机与提前初始化 |
| 处置要求 | 必须修复 | 必须同时修复 |
1.1 受影响部署应如何理解
PHP 官方将所有处理 Web 请求的 PHP 部署列入风险范围,包括 PHP-FPM、Apache mod_php、php-cgi、FrankenPHP 和 LiteSpeed。已演示的 mod_php 攻击可以发生在应用代码执行之前,因此仅依赖 PHP 应用中的登录检查、框架中间件或上传校验,并不能覆盖这种路径。S1
需要区分“部署需要修复”与“某一攻击链已被完整证明”。CVE-2026-109618 的公告说明:FPM 在请求启动失败后会退出子进程,阻断了 mod_php 对应的启动失败后清理路径;但启动成功后的 OOM 路径仍存在风险。该公告同时注明,针对这一 CVE 的完整 FPM 利用尚未演示。这个限制不能据此推导出 FPM 安全,尤其不能忽略 CVE-2026-109619。S2S3
成功利用的直接权限边界取决于承载 PHP 的进程身份及其可访问资源。通常应关注网站代码、上传目录、数据库凭据、服务令牌和内部网络访问能力;“远程代码执行”不等于天然获得 root 权限。这里的后果判断基于进程权限模型,实际影响需要结合部署权限评估。
普通一次性 CLI 脚本不具备同样的 HTTP 入口;但以 CLI 或嵌入方式长期处理外部任务的进程,仍应纳入运行时与生命周期盘点。只更新 CMS、框架或 Composer 依赖,也不会自动替换底层 PHP 引擎。
2. 成因分析
2.1 先理解两个生命周期
PHP worker 往往连续处理多个请求。请求期间分配的内存可以在请求结束时统一释放,而 worker 本身及其部分全局字段继续存在。
因此,引擎必须维持两项约束:
- 初始化约束:对其他代码可见的数据,必须处于可安全读取、遍历和销毁的状态。
- 生命周期约束:请求内存释放后,存活更久的字段不能继续把其中的地址当成有效状态。
这两项约束分别对应两组漏洞的主要失败方式。以下流程是结合公告与补丁整理的机制说明,不代表每种部署都存在完全相同的可达路径。S2S3S5
2.2 CVE-2026-109618:OOM 打断了尚未完成的状态更新
旧实现使用 longjmp() 一类非局部跳转来处理 OOM,相关路径通过 zend_bailout() 退出当前执行上下文。危险在于,一段 C 代码可能已经将新对象、数组或请求状态发布给其他代码,但还未完成全部字段初始化;此时任意一次相关时机上的分配失败,都可能使正常的收尾步骤被跳过。S2
可以把失败过程拆成四步:
- 创建或更新请求内部结构,并使其变得可访问。
- 后续内存分配失败,触发 OOM。
- 执行流跳离当前函数,原本用于完成初始化或恢复状态的代码没有执行。
- 请求清理继续处理这些结构,将无效内容当成有效字段或地址。
这里的关键不是“内存用完就一定产生 RCE”,而是内存不足改变了控制流,破坏了引擎对结构有效性的假设。失败的分配也不必与最终被错误读取的结构直接相关。S2
为什么较小的请求也可能耗尽内存
请求体字节数与解析后的堆占用不是一回事。一个字段在 PHP 内部可能对应多个字符串、数组元素和元数据对象;输入的结构复杂度会影响内存放大程度。
官方公告列出的 multipart 问题包括:缺少可用声明长度时的初始大小检查缺口、单个 part 的头部数量未受到充分限制,以及空文件控件仍产生元数据、嵌套名称进一步增加数组工作量。应用自身对输入的展开、解码或重复分配,也可能成为 OOM 来源。S2
由此可得两条防护结论:
- 请求体限制必须尽量在进入 PHP 前执行,并考虑实际读取长度与结构复杂度。
- 单独限制文件大小、文件数量,或只关闭一个上传入口,无法覆盖所有 OOM 来源。
为什么应用鉴权可能来不及
在 mod_php 中,请求初始化阶段就可能自动解析请求体;即使启动失败,相关请求清理仍会执行。已确认攻击路径因此可以先于应用代码发生。若要在这个阶段减少暴露,限制需要放在反向代理、Web 服务器或其他位于 PHP 请求处理之前的边界。S1S2
2.3 CVE-2026-109619:请求内存已释放,全局引用仍然存活
这一问题涉及错误、session、对象或扩展等状态:字段保存在 worker 可长期访问的位置,字段引用的数据却属于某一次请求。只要引用在请求堆释放前未被正确清理,后续请求就可能遇到悬空状态,形成 use-after-free。S3
官方分析将可利用条件分为“生产者”和“消费者”:
| 角色或条件 | 含义 | 对防护的启示 |
|---|---|---|
| 状态生产者 | 把本请求拥有的状态放入稍后仍可访问的位置 | 要审视异常退出后是否完成对应清理 |
| 状态残留 | 没有清空,或清空后又被晚期操作重新填入 | 仅在较早的 shutdown 阶段清空一次可能不够 |
| worker 复用 | 同一 worker 继续处理后续请求 | 多 worker 只是调度差异,不是内存安全保证 |
| 提前消费者 | 在对应状态正常初始化前读取、释放或判断残留值 | 必须检查初始化之前的 INI、session 等路径 |
这些条件需要组合成立;任意一次错误日志本身并不证明漏洞已经被成功利用。S3
它不以 OOM 为必要前提
致命错误可能使成对的状态发布与恢复过程被打断。例如编译或解析失败、未捕获错误、require 失败及执行超时,都可能参与特定状态的残留。清理后期的非致命诊断还可能重新填入已经清空过的错误信息。S3
因此,将 memory_limit 改为无限制,不能覆盖这组漏洞。
“初始化之前”需要分别看待
两类已确认消费者的时序不同:
- 早期 INI 诊断:某些按请求应用的配置在普通 executor 初始化前发出 warning、notice 或 deprecation,诊断处理可能访问旧错误状态。
- session upload progress:请求体解析过程中可能提前处理 session,而常规模块
RINIT尚未完成;不能以应用没有显式调用session_start()推断该路径不存在。S3
并非所有 INI 配置都具有相同风险时机。mod_php 的目录级配置与 FPM 前端传入的 PHP_VALUE / PHP_ADMIN_VALUE,需要与普通 php.ini、FPM pool 启动配置及 FPM .user.ini 区分。后几者的应用时机不同,不能一概称为同一种早期消费者。S3
关闭 display_errors、log_errors,降低 error_reporting,或者使用 @,只影响诊断的可见性;相关内部状态可能在决定是否输出日志之前已经被访问。S3
3. 修复版本与补丁选择
3.1 上游正式修复基线
| 当前 PHP 分支 | 首个包含本次正式修复的版本 | 建议 |
|---|---|---|
| 8.2 | 8.2.35 | 升级至该版本或包含相同修复的后续版本 |
| 8.3 | 8.3.36 | 同上 |
| 8.4 | 8.4.27 | 同上 |
| 8.5 | 8.5.12 | 同上 |
| 8.1 及更早版本 | 上游常规支持已结束 | 优先迁移至受支持分支;短期核对发行版或 LTS 厂商回补 |
版本与支持状态依据 PHP 官方公告,时间基线为 2026 年 10 月 22 日。S1
这些是分支内的修复门槛,不能用一句“所有低于 8.5.12 的版本均未修复”替代。8.2.35 与 8.4.27 等其他分支版本已经包含修复。
3.2 发行版版本号不能只看上游部分
发行版常在较旧的上游版本号基础上回补安全修复。验收应同时检查发行版、仓库来源、完整包版本和安全公告。
例如,Ubuntu USN-8948-1 列出的 Ubuntu 24.04 LTS 修复包版本为:
8.3.6-0ubuntu0.24.04.13
它的上游部分仍是 8.3.6,但该发行版完整包版本已列入本次修复公告。这一结论只适用于对应发行版和包来源,不能推广到任意同样显示 8.3.6 的构建。S10
3.3 实际应选择哪一种 patch
| 场景 | 优先方案 | 验收重点 |
|---|---|---|
| 使用发行版 PHP 包 | 安装厂商发布的安全更新 | 公告同时覆盖两个 CVE;完整包版本正确 |
| 使用官方 PHP 容器或自建镜像 | 更新基础运行时,重新构建并部署 | 运行实例使用新镜像;旧副本全部退出 |
| 自编译受支持分支 | 使用该分支正式安全版本或完整官方补丁集 | 分支匹配、依赖补齐、扩展兼容与回归通过 |
| 受制于 7.4、8.0、8.1 | 优先厂商回补;必要时评估官方应急源码分支 | 明确行为变化,并安排迁移至受支持版本 |
官方已发布补丁,实际修复应从这些补丁出发。第 6 节中的代码片段用于说明设计与审阅重点,不构成可单独应用的完整补丁。S1S4S6
4. 正式修复流程
4.1 盘点真正处理请求的运行时
建议形成一张简短台账,至少记录:
- 入口域名或服务、反向代理后端、PHP SAPI。
- PHP 分支、完整软件包版本或镜像摘要、构建来源。
- FPM pool、实际服务单元、容器或 Pod、副本数量。
- 已加载扩展、主配置与额外配置文件。
- 是否存在其他 PHP 版本、旧部署副本或可以直接访问的源站入口。
这些是实施建议,目的是使“已升级”能对应到每个实际流量入口。特别检查命令行 PHP 与 Web PHP 使用不同版本、镜像构建完成但旧容器仍运行、部分 pool 或副本未替换等情况。
4.2 选择并部署正确更新
按第 3 节确认修复版本,优先使用既有可信包源或受控构建链。更新范围应包含实际承载请求的组件,例如 FPM 包、Apache PHP 模块、CGI 二进制,或将 PHP 嵌入其中的服务构建。
升级前保留配置与包清单,在与生产相近的环境先验证启动和关键业务。对于自行维护的扩展,检查对应 PHP 版本的构建与运行兼容性。只从另一个分支挑取一个看似相关的提交,不能保证整个修复集已完整落地。
4.3 重启或滚动替换运行实例
安装文件与进程加载的新代码是两件事。应重启对应服务,或完成滚动替换,并确认旧 worker 与旧容器已经退出。
对于 mod_php,重启对象通常是承载模块的 Apache 进程;对于嵌入 PHP 的服务,需要替换承载进程本身。对 FPM 而言,清理 OPcache 不能替代运行时更新与旧 worker 退出。
采用优雅切换时,需要检查旧代进程是否仍处理长连接或未完成请求。验收记录应包含实际服务、部署版本、变更完成时间及旧实例退出结果。
4.4 从真实 Web 请求验证结果
推荐把下面几项证据组合起来,而不是仅依赖单条版本命令:
| 证据 | 能证明什么 | 不能单独证明什么 |
|---|---|---|
| 包管理器或镜像元数据 | 安装来源与完整构建版本 | 流量已经切换到该实例 |
| 进程及部署状态 | worker 或容器完成替换 | 对外入口一定路由到它 |
| 经真实入口访问的受限诊断结果 | 实际 Web SAPI 和运行配置 | 发行版回补是否覆盖两个 CVE |
| 厂商公告或完整补丁记录 | 本次漏洞修复包含在该构建中 | 所有副本均已部署 |
可通过已有内部诊断能力,或临时且受访问控制的诊断入口,核对 PHP_VERSION、PHP_SAPI、加载的配置文件及应急配置。确保请求没有被 CDN 或页面缓存直接返回;完成后移除临时诊断入口。
php -v 通常只说明当前 shell 选择的 CLI 二进制;php-fpm -i 等命令也不等于对现存、正在接收流量的 worker 做了验证。不要将完整 phpinfo() 页面长期暴露到公网。
4.5 业务回归与应急配置退出
围绕本次改动影响的功能验证正常登录、session 读写、表单与上传、导入、错误处理和 worker 稳定性。检查错误率、响应延迟、内存峰值、进程退出及重建频率是否异常。
若临时设置了 memory_limit=-1,在正式补丁生效后恢复与业务容量匹配的有限预算;若设置过 pm.max_requests=1,在确认修复后恢复经过容量评估的生命周期配置。输入大小限制和来源访问控制可按业务需要保留。
若升级出现兼容性问题,应优先暂停受影响入口、限制其外部访问或调整应用,再处理回退。直接让未修复运行时重新对公网服务,会重新打开原有风险。
5. 无法立即升级时的缓解措施
本节用于短期降低暴露或改善可用性。每项措施只覆盖特定路径,多个措施组合也不等价于完整修复。S1S2S3
5.1 优先控制进入 PHP 的流量
如果业务允许,将敏感服务临时限制为可信来源,或在 PHP 之前执行访问控制。需要上传的业务收紧请求体、字段、part、part 内头部数量及嵌套深度;不需要 multipart 的业务可临时在前端拒绝这类请求。S2
这些限制要覆盖所有通往 PHP 的入口。仅拦截名为“上传”的 URL,会漏掉其他仍由 PHP 自动解析请求体的入口。对于分块传输、缺少可用声明长度等情况,应以实际读取量执行限制;应用再次解压或展开内容时,还要有自己的输出大小预算。
5.2 FPM pool 配置模板
以下内容放在实际生效的 FPM pool 配置段中,不能原样放入 php.ini。这里的 8M 和 4M 是演示值,应根据业务调整;限制整个 POST 请求时还要为表单字段与 multipart 边界保留空间。S11S12
; Temporary mitigation: evaluate capacity before applying.
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
仅 PHP 8.5,另加:
php_admin_flag[fatal_error_backtraces] = off
各项作用与边界如下:
| 配置 | 预期作用 | 必须保留的限制说明 |
|---|---|---|
memory_limit=-1 | 去掉可预测的 PHP 请求内存上限触发条件 | 不防底层分配失败;取消了原有预算,可能增加内存耗尽和服务中断风险 |
较小的 post_max_size | 缩小进入解析流程的数据规模 | 未修复解析器有已知缺口;不能单独阻止内存放大 |
upload_max_filesize < post_max_size | 限制单文件大小并保留请求开销空间 | 不约束全部字段、元数据或应用内分配 |
| 关闭 upload progress | 阻断已确认的 session 相关路径 | 不覆盖其他全局状态变体 |
| PHP 8.5 关闭 fatal backtrace | 阻断已确认的相关状态生产者 | 不覆盖 session 等其他路径 |
措施边界来自官方公告;pool 语法与管理级配置语义见 PHP 手册。S1S2S3S11
管理级 php_admin_* 设置不能由应用通过 ini_set() 改写。仍应审查所有 pool、目录级与前端传入配置,确认实际请求上的值符合预期。修改后重建全部 worker,避免已有进程继续持有旧状态。
如果选择直接修改 php.ini,语法应改为:
memory_limit = -1
post_max_size = 8M
upload_max_filesize = 4M
session.upload_progress.enabled = Off
与 pool 中的 php_admin_value 不同,仅在 php.ini 设置 memory_limit=-1 不能阻止应用再次修改它。应检查框架、应用代码及其他配置是否重新设置有限值;需要强制执行时,使用适合当前 SAPI 的管理级设置,并核对实际生效值。S11S12
PHP 8.5 才追加 fatal_error_backtraces = Off。不要在应用代码中使用 ini_set() 作为关闭 upload progress 的补救,也不要依赖 FPM .user.ini 来保证它在请求解析前生效。S1S3
5.3 取消 PHP 内存上限时如何控制代价
memory_limit=-1 是未修复环境的临时路径缓解,不应作为长期默认配置照搬。应用或请求失控时,它可能耗尽更多宿主机内存。因此,使用前应评估并发数、单 worker 峰值及服务总容量,同时部署前端输入限制。S2
容器或 cgroup 内存限制可以提供可用性边界,但不能保证分配永不失败;限制触发时,还可能终止整个容器、master 和全部 worker。隔离粒度及终止策略需要结合部署评估,不能将其称为这两个 CVE 的完整防护。S2
不要以 RLIMIT_AS、RLIMIT_DATA 或对应的进程地址空间限制替代上述策略;它们可能直接造成底层分配失败,进入仍然有问题的 OOM 路径。S2
5.4 Nginx 配置示例
适用于 Nginx 向 PHP-FPM 转发请求的部署。可在实际承载服务的配置层级设置:
client_max_body_size 8m;
fastcgi_request_buffering on;
client_max_body_size 限制请求体大小;fastcgi_request_buffering on 让 Nginx 先读取完整请求体,再向 FastCGI 后端发送。是否在某个 server 或 location 中生效,需要结合已有继承和覆盖关系检查。S13S14
仅在业务完全不需要 multipart 时,可在对应服务入口追加拒绝规则:
if ($http_content_type ~* "multipart/form-data") {
return 403;
}
这是使用 if 和 return 的简单入口限制模板,不是解析器漏洞的通用过滤规则。部署前验证配置语法与正常请求,并检查自定义 error_page 是否把 403 请求重新交给 PHP。字符串匹配对所有解析差异不提供完整保证。S15
此外,这些配置并不自动限制应用自行解压后的数据量、字段数量或 PHP 对象数;需要上传的业务应使用能够理解其请求结构的额外限制。
5.5 可选措施:FPM 每个 worker 只处理一次请求
对能承受开销的 FPM 部署,可评估短期设置:
pm.max_requests = 1
并重建已有 worker。它在 worker 复用层面阻断已描述的跨请求链,但会增加进程创建和初始化开销;同时不能修复 CVE-2026-109618 的同请求 OOM 与清理问题。值较小但大于 1 时,仍然存在请求复用窗口。S3S11
5.6 WAF 与 Cloudflare 的适用边界
Cloudflare 已公布针对 PHP2Shell 的防护,包括 Free 计划在内的部署安排。使用其服务时,应核对实际流量是否经过代理、防护规则是否生效,以及源站能否被直接绕过;付费计划还需检查相应 Managed Rules 的配置与例外。S16
该厂商说明的部分检测利用响应信号,因此不能将所有防护理解成“每次攻击都在到达 PHP 之前被拦截”。WAF 可辅助减少暴露并提供观察信息,但继续升级 PHP 仍是必要工作。S16
5.7 常见措施为什么不足
| 措施或判断 | 不足之处 |
|---|---|
只关闭 file_uploads | 不能覆盖全部 multipart 处理和应用自身的 OOM 路径 |
设置 enable_post_data_reading=Off | 改变自动填充 POST/FILES 的行为;请求体仍可由应用读取,还可能破坏正常业务 |
关闭错误显示、日志或使用 @ | 内部诊断处理仍可能访问悬空状态 |
| 增加 worker 数量 | 不建立安全的状态清理机制,也不消除单 worker 复用 |
仅配置 disable_functions 或 open_basedir | 这些限制不能修复 C 引擎层内存破坏 |
| 只检查一次版本或单个副本 | 不能证明所有入口与运行实例都已替换 |
| 没有看到异常日志 | 不能证明漏洞不存在,也不能证明此前未被利用 |
其中 PHP 配置与漏洞机制依据见 S2S3S12;关于覆盖、验收和权限边界的判断是基于这些机制提出的实施建议。
6. 官方 patch 的实现思路与审阅重点
6.1 应使用完整、匹配分支的修复
PHP 8.5 的代表性官方提交为:
31c0ea7d7ff1d1fccb6492e52dab544011628065
Avoid state corruption via bailout
提交明确关联两个 CVE,既调整 OOM 退出机制,也修复可能在初始化阶段访问的全局状态。提交页面与完整 diff 见 S4S5。其他分支应使用自己的正式安全发布或匹配补丁,不能假设 8.5 的改动可直接应用到任意旧版本。
6.2 OOM 修复:先恢复可处理状态,再终止请求
正式实现引入了软、硬两个层次的 OOM 处理:达到软限制时记录待处理状态,并在 VM 等安全位置进行受控退出;可失败分配接口则允许调用方显式处理失败。对于不能直接返回失败的分配,若预计超过更高的硬性阈值,便直接终止进程,避免在不安全的中间状态继续处理请求。S2S5
以下是 8.5 diff 中用于标记待处理 OOM 的关键代码节选:
EG(delayed_oom).state = ZEND_OOM_STATE_PENDING;
zend_atomic_bool_store_ex(&EG(vm_interrupt), true);
VM 中断处理及其他安全位置随后调用 zend_handle_delayed_oom()。其用户代码执行上下文使用受控的 unwind 路径;硬 OOM 路径调用 _exit(137)。S5
从设计上看,重要变化是:避免在结构正处于中间状态时随意跨越 C 调用栈,把退出放在能够维护状态约束的位置。软限制触发后可能需要额外内存完成这一过程,因此不能把新的软阈值理解为进程总内存的绝对上限。
也不能概括成“PHP 已删除所有 zend_bailout()”。补丁仍在特定上下文使用相关机制;修复的是危险的退出时机及与之配套的状态管理。S5
6.3 分配调用者与 multipart 解析必须一起修改
如果只把一个分配函数改成“失败返回 NULL”,而原来的调用者仍假定永远返回有效指针,就会引入新的空指针访问或未完成清理。补丁中的可失败分配接口,需要与调用方检查及异常传播共同审阅。S5
multipart 相关修复还包括按实际读取量执行累计检查、减少无用或重复头部的处理,并在 OOM 待处理状态下采用一致的退出与清理逻辑。这些改动既缩小资源放大面,也使解析器遵守新的 OOM 协议。S5
对维护自有分支的团队,建议把以下问题作为代码审阅重点:
- 状态在完全初始化前是否已对外可见?
- 分配失败或回调异常后,所有权是否仍然唯一且明确?
- 中途失败能否到达统一清理出口?
- 清理函数能否识别未初始化或已释放字段?
- 解析预算是否以实际处理量为依据,而不仅信任请求声明?
- 新的失败返回值是否已被所有调用者处理?
这些是依据修复机制归纳的审阅问题,并非对某个未提供代码库已经完成的审计结论。
6.4 跨请求修复:初始化更早,最终清理更晚
对应 CVE-2026-109619,补丁同时调整多个生命周期边界。下面列出的文件和操作以 PHP 8.5 官方 diff 为例。S5
| 位置 | 改动方向 | 解决的问题 |
|---|---|---|
Zend/zend_execute_API.c 等 | 复位错误、异常、handler、对象等执行器状态 | 请求结束后仍存在可被下一请求访问的旧引用 |
main/main.c 的启动路径 | 在 SAPI 激活前调用 php_basic_early_rinit() | 提前消费者发生时,相关基础状态尚未初始化 |
main/main.c 的关闭路径 | 销毁 HTTP globals 后标记为未定义 | 释放后的值仍被当成有效对象 |
| 请求堆最终释放之前 | 再次清理最后错误信息 | 较晚的清理诊断重新填入错误状态 |
ext/session/session.c | upload progress 早期路径先初始化 session globals | 常规模块 RINIT 之前访问旧状态 |
| 序列化相关代码 | 早期处理中限制类解析与用户回调 | 初始化未完成时执行额外用户代码 |
例如,启动顺序中加入了:
zend_activate();
php_basic_early_rinit();
sapi_activate();
HTTP globals 销毁后补上状态失效标记:
zval_ptr_dtor(&PG(http_globals)[i]);
ZVAL_UNDEF(&PG(http_globals)[i]);
这些节选表达了两项通用原则:第一次可能使用某类状态之前就要初始化;最后一次可能重新产生该状态之后才进行最终清理。 只在常规 RINIT 或较早的 shutdown 中修一次指针,可能遗漏更早或更晚的访问。S5
6.5 为什么不建议自制“一行修复”
把所有 zend_bailout() 替换成 _exit(),或者仅在某个入口加一个空指针检查,都会跨越大量不同的执行上下文。它们可能遗漏其他状态字段,也可能改变析构、扩展回调、进程宿主和请求结束语义。
对于受支持版本,优先采用已经发布的完整修复。自有分支的移植应记录补丁来源、适用基线、冲突处理和行为变化,并使用对应上游回归用例及本业务流程验证。这里的建议来自对补丁跨 allocator、VM、session 与请求生命周期改动范围的分析。S4S5
7. PHP 7.4、8.0、8.1 的应急回补
PHP 官方针对严重性提供了三个旧分支的最小源码回补,分支名分别为:
PHP-7.4-CVE-2026-109618-109619
PHP-8.0-CVE-2026-109618-109619
PHP-8.1-CVE-2026-109618-109619
分支链接见 S7S8S9。这些是针对本次漏洞的应急交付,不表示上游恢复对旧分支的常规支持,也不替代其他安全更新。S1
7.1 以 PHP 8.1 回补为例
PHP 8.1 的相关提交是:
1d7dec178a094e6e75b229ce22754045f8c6070a
其策略更偏向快速失败:在相应 OOM 路径中输出诊断后调用 _exit(1),并包含全局状态及其他相关修正;同时在内部禁用 session upload progress 的相应处理。相关源码提交见 S6。
这与受支持分支中较完整的延迟 OOM 设计存在行为差异。不能仅因为配置里仍显示 upload progress 开启,就认为经过该回补的内部处理行为也保持不变。
7.2 部署时需要接受的代价
直接退出进程可能使当前请求失败,跳过正常 PHP shutdown 回调及部分应用收尾;若宿主采用线程模型,还需评估整个宿主进程退出对其他并发工作造成的影响。进程管理器是否能及时拉起替代进程,也应纳入验证。
因此,旧版本用户应按顺序考虑:
- 迁移至受支持 PHP 分支及其修复版本。
- 使用明确覆盖两个 CVE 的发行版或 LTS 厂商更新。
- 确实受制于旧版源码时,采用匹配版本的官方应急回补,验证完整改动与兼容性,并安排后续迁移。
这里的“最小回补”描述的是官方选择的兼容性与实现范围,不应自动解释为“已知不完整的漏洞修复”。应按对应分支源码和厂商公告判断,不自行删减其中的配套改动。S1S6
8. 验收标准与后续检查
建议以以下结果作为本次修复完成的依据:
| 验收项 | 完成标准 |
|---|---|
| 漏洞覆盖 | 构建或厂商公告明确修复 CVE-2026-109618 和 CVE-2026-109619 |
| 部署覆盖 | 全部入口、pool、服务及副本使用修复后的运行时 |
| 进程生效 | 旧 worker、旧宿主进程和旧容器全部退出 |
| 真实请求验证 | 经实际 Web 链路确认 SAPI、构建和配置,排除缓存误导 |
| 业务可用性 | session、上传、导入、错误处理等关键功能通过正常回归 |
| 临时措施收尾 | 无限内存及极端 worker 回收配置已按容量计划恢复或有明确保留原因 |
| 变更证据 | 记录包版本或镜像摘要、更新范围、完成时间与验证结果 |
上述为实施建议。若需验证 OOM 行为、异常退出或自定义补丁,应在隔离环境中使用上游回归测试或受控故障注入,避免以生产环境反复制造内存耗尽作为验收方式。
同时检查更新前后的 PHP fatal、worker 崩溃、异常重启、请求错误率及 WAF 事件。日志只能提供调查线索:出现 OOM 不等于已经被利用,缺少告警也不能证明未发生过利用。升级修复的是未来可达的漏洞路径;如已发现文件篡改或凭据泄露等异常,应另行开展事件调查。
附录:引用来源与链接
所有链接均集中在本附录。漏洞公告、补丁和厂商通告以 2026 年 10 月 22 日 的核验结果为基线;后续部署应继续查看供应商更新。正文 [Sx] 对应以下编号。
S1 — PHP 官方安全公告与修复版本
- 来源:PHP 项目,2026 年 10 月 21 日。
- 用途:风险范围、四个正式修复版本、官方临时措施及旧分支应急回补说明。
- 链接:PHP 2026 年公告归档中的本次安全公告
S2 — CVE-2026-109618 官方技术公告
- 来源:php/php-src GitHub Security Advisory,2026 年 10 月 21 日。
- 标识:GHSA-w8hw-w9fx-j79w。
- 用途:OOM、multipart 放大、SAPI 差异与缓解措施的覆盖边界。
- 链接:RCE via OOM corruption
S3 — CVE-2026-109619 官方技术公告
- 来源:php/php-src GitHub Security Advisory,2026 年 10 月 21 日。
- 标识:GHSA-6wmf-4rv5-p3rp。
- 用途:跨请求悬空状态、生产者与消费者、提前初始化、配置缓解。
- 链接:RCE via stale global state
S4 — PHP 8.5 官方修复提交
- 来源:php/php-src。
- 提交:
31c0ea7d7ff1d1fccb6492e52dab544011628065。 - 用途:确认正式提交同时修复两项漏洞,并说明整体改动方向。
- 链接:Avoid state corruption via bailout
S5 — PHP 8.5 官方修复完整 diff
- 来源:同一官方提交的 diff。
- 用途:核对 allocator、VM、HTTP globals、session、序列化和请求生命周期的具体实现。
- 链接:官方完整 diff
S6 — PHP 8.1 官方应急回补提交
- 来源:php/php-src。
- 提交:
1d7dec178a094e6e75b229ce22754045f8c6070a。 - 用途:核对旧分支快速退出及 upload progress 行为变化。
- 链接:PHP 8.1 应急回补提交
- 链接:对应完整 diff
S7 — PHP 7.4 官方应急回补分支
- 来源:php/php-src,PHP 官方安全公告所列源码分支。
- 链接:PHP-7.4-CVE-2026-109618-109619
S8 — PHP 8.0 官方应急回补分支
- 来源:php/php-src,PHP 官方安全公告所列源码分支。
- 链接:PHP-8.0-CVE-2026-109618-109619
S9 — PHP 8.1 官方应急回补分支
- 来源:php/php-src,PHP 官方安全公告所列源码分支。
- 链接:PHP-8.1-CVE-2026-109618-109619
S10 — Ubuntu 安全公告 USN-8948-1
- 来源:Canonical / Ubuntu,2026 年 10 月 21 日。
- 用途:发行版回补及完整软件包版本示例。
- 链接:USN-8948-1: PHP vulnerabilities
S11 — PHP-FPM 配置手册
- 来源:PHP 官方文档。
- 用途:
php_admin_value、php_admin_flag、pm.max_requests等配置语义。 - 链接:PHP-FPM Configuration
S12 — PHP 核心 INI 配置手册
- 来源:PHP 官方文档。
- 用途:请求体、上传、内存与自动 POST 读取配置的定义和适用范围。
- 链接:Description of core php.ini directives
S13 — Nginx 请求体大小限制
- 来源:Nginx 官方文档。
- 用途:
client_max_body_size的含义及可用配置层级。 - 链接:ngx_http_core_module / client_max_body_size
S14 — Nginx FastCGI 请求缓冲
- 来源:Nginx 官方文档。
- 用途:
fastcgi_request_buffering的处理顺序与作用。 - 链接:ngx_http_fastcgi_module / fastcgi_request_buffering
S15 — Nginx 条件与返回指令
- 来源:Nginx 官方文档。
- 用途:
if与return的配置语法。 - 链接:ngx_http_rewrite_module
S16 — Cloudflare PHP2Shell 防护说明
- 来源:Cloudflare 官方博客。
- 用途:其 WAF 部署范围、Free 计划安排及检测机制说明。
- 链接:Cloudflare deploys protections against php2shell vulnerability