PHP 的内存管理、异常退出机制与 Session 上传进度功能之间,可能存在比单个内存错误更复杂的安全问题。本文围绕 CVE-2026-109618 和 CVE-2026-109619,梳理相关漏洞的成因、攻击链、修复思路与防护措施。
安全提示: 本文用于漏洞原理分析、版本核查和防御加固。具体漏洞是否可利用,取决于 PHP 版本、构建方式、运行环境及应用配置。请勿在未经授权的系统上测试漏洞。
一、漏洞概述
PHP 的内存分配器负责管理运行时分配的内存。当进程遇到严重内存错误时,运行时需要终止当前执行流程,避免程序继续使用已经损坏的内部状态。
问题在于,PHP 并非所有错误都会通过普通的函数返回值传递。一些致命错误会进入 bailout 机制,通过非局部跳转退出当前执行路径。如果内存分配器、Zend 引擎与扩展模块之间的状态没有正确恢复,异常退出就可能影响后续代码执行。
本文关注的两个漏洞编号为:
- CVE-2026-109618:与 PHP 在特定异常退出路径中的内部状态损坏有关。
- CVE-2026-109619:与前述异常状态在后续请求中的影响及潜在内存安全问题有关。
两者需要结合分析:第一个问题关注异常发生时的状态一致性,第二个问题关注异常状态如何进一步影响后续执行。
需要强调的是,漏洞编号、受影响版本、CVSS 评分和修复版本应以 PHP 官方安全公告及对应发行版公告为准。不要仅凭第三方文章中的版本号或评分判断系统是否安全。
二、理解 PHP 的 bailout 机制
2.1 普通错误与 bailout 的区别
普通错误通常可以通过返回值、异常或错误处理机制进行处理。例如:
1 | $result = some_operation(); |
但 Zend 引擎中的部分致命错误无法按照普通业务逻辑继续执行。在某些路径上,PHP 会通过 bailout 机制退出当前执行流程。
从概念上看,相关调用链可能涉及以下组件:
1 | flowchart TD |
在源码分析中,可以重点关注以下符号:
zend_mm_safe_error()zend_error_noreturn()zend_bailout()
这些函数分别涉及内存管理错误、不可返回的错误处理路径以及 bailout 退出机制。具体调用关系会随 PHP 版本和代码路径而变化,因此不能将上述示意图理解为所有内存错误都会经过的固定调用链。
2.2 为什么非局部跳转需要特别谨慎
longjmp 一类的非局部跳转可以直接离开当前调用栈中的部分函数,而不必像正常返回那样逐层执行后续代码。
这意味着,如果某个函数在执行过程中修改了全局状态、内存分配器状态或请求上下文,那么异常退出时就必须确保这些状态仍然一致。
例如,假设一个组件正在执行以下操作:
- 修改内部状态;
- 分配或释放内存;
- 调用可能触发致命错误的操作;
- 依赖正常返回路径恢复状态。
如果第三步触发了 bailout,第四步就可能被跳过。如果必要的恢复逻辑没有安排在可靠的清理路径中,内部状态就可能残留在中间状态。
这类问题通常被称为异常安全性问题或状态一致性问题。当残留状态进一步影响内存生命周期时,就可能演变为更严重的内存安全漏洞。
三、CVE-2026-109618:异常退出导致的状态损坏
3.1 核心问题
CVE-2026-109618 所涉及的核心问题,是特定异常退出路径中 PHP 内部状态的一致性。
分析此类漏洞时,需要区分两个层面:
- 错误本身: 内存分配失败或其他致命条件触发异常退出。
- 安全影响: 异常退出后,内部状态是否仍然有效,后续执行是否会访问已经失效或不符合预期的对象。
内存不足本身并不必然意味着远程代码执行。真正需要关注的是,程序是否在异常退出后继续使用了被破坏的状态,以及攻击者能否通过输入控制错误发生的位置和后续执行过程。
3.2 OOM 与内存管理器
OOM 是 Out of Memory 的缩写,即内存耗尽。它可能由进程内存限制、系统可用内存不足、异常大的请求或内存使用失控等原因触发。
在 PHP 中,内存分配失败可能进入 Zend 内存管理器的错误处理路径。此时,引擎需要在终止执行、清理资源和维持内部状态一致性之间作出正确处理。
如果错误处理流程本身依赖额外的内存分配,或者在异常退出前修改了关键状态,就可能形成复杂的失败路径。
从安全审计角度看,应重点检查:
- 错误处理函数是否可能再次触发内存分配;
- 异常退出是否绕过必要的状态恢复;
- 分配器状态是否会在请求结束后继续存在;
- 后续请求是否会接触此前请求留下的对象或指针;
- 相关扩展是否假设某些清理函数一定会正常执行。
这些问题并非 PHP 独有。在 C、C++ 等需要显式管理资源生命周期的运行时中,异常安全与资源清理同样是重要的安全审计方向。
3.3 为什么不能把所有 OOM 都视为可利用漏洞
即使可以通过请求触发内存耗尽,也不代表攻击者能够获得代码执行能力。
实际影响取决于多个条件:
- 目标是否运行受影响的 PHP 版本;
- 请求是否能够进入相关错误路径;
- 异常退出是否导致可观察的状态损坏;
- 损坏状态是否会在后续执行中被使用;
- 攻击者能否控制后续执行所依赖的数据;
- 运行环境是否存在额外的可利用条件。
因此,内存耗尽、进程崩溃、拒绝服务、内存破坏和远程代码执行是不同的安全影响,不能直接画等号。
四、CVE-2026-109619:跨请求状态与内存生命周期
4.1 为什么 PHP-FPM 的请求生命周期很重要
PHP-FPM 通常由多个长期运行的工作进程处理 HTTP 请求。与每个请求都启动一个全新进程的模型不同,PHP-FPM 工作进程在处理完一个请求后,可能继续处理其他请求。
其基本流程如下:
1 | flowchart TD |
这种设计能够降低进程启动开销,但也要求 PHP 在请求边界正确清理请求级状态。
如果某次请求在异常退出过程中未能完成预期的清理,且受影响的状态没有在下一次请求开始前被重置,那么后续请求就可能遇到不一致的运行环境。
这也是分析长期运行服务中的状态问题时,必须考虑请求复用机制的原因。
4.2 Use-after-free 的风险
Use-after-free(UAF,释放后使用)是指程序释放了一块内存后,仍然通过旧指针访问这块内存。
一个简化的示例:
1 | char *ptr = malloc(64); |
在真实漏洞中,情况往往更复杂:指针可能保存在对象字段、全局状态、扩展内部结构或其他间接引用中。
如果请求 A 在异常路径中留下失效引用,而请求 B 又在不合适的时机使用该引用,就可能产生跨请求的内存安全问题。
不过,需要注意:不能仅凭“跨请求状态残留”就断言一定存在 UAF,更不能直接推导出 RCE。 必须结合具体版本的源码、补丁差异和可重复的测试结果确认。
4.3 Session 上传进度功能的相关性
PHP 的 Session 上传进度功能用于在文件上传过程中记录进度信息。相关配置通常包括:
1 | session.upload_progress.enabled = On |
该功能可能在请求处理过程中参与 Session 状态更新,并与 multipart/form-data 请求及文件上传流程发生交互。
在分析涉及上传进度的漏洞时,需要检查:
- 上传进度功能是否启用;
- 请求是否包含符合条件的 multipart 数据;
- Session 状态何时初始化和清理;
- 内存分配失败是否可能打断正常处理流程;
- bailout 之后是否仍有状态被后续请求访问。
这些因素可以帮助解释漏洞的触发条件,但不能据此认定所有启用上传进度功能的服务器都可被远程利用。
五、从状态损坏到远程代码执行:需要哪些条件
远程代码执行(RCE)意味着攻击者能够让目标系统执行其控制的代码。这通常需要一条完整的利用链,而不仅仅是触发一次错误。
概念上,潜在利用过程可能包含以下阶段:
1 | flowchart TD |
这只是用于理解漏洞风险的抽象模型,不代表每个受影响版本都能完成所有阶段。
实际利用难度还可能受到以下因素影响:
- PHP 分支版本和具体补丁状态;
- CPU 架构与编译器行为;
- PHP 的编译选项;
- PHP-FPM 的进程管理和请求分配方式;
- 扩展模块及其内存分配行为;
- 操作系统安全机制;
- ASLR、NX 等缓解措施;
- 请求是否能够稳定地进入预期执行路径。
因此,漏洞的严重程度与实际利用成功率是两个不同的问题。安全管理者应优先修复受影响版本,而不是以“暂时没有可用利用程序”为理由延迟更新。
六、官方补丁应重点检查什么
分析安全补丁时,不能只看提交标题或新增的几行代码。更可靠的方法是同时检查漏洞公告、代码差异、测试用例和发行说明。
可从以下几个方面理解修复思路。
6.1 保证异常退出路径的状态一致性
对于可能触发 bailout 的代码,需要检查在错误发生时哪些状态已经发生变化,以及哪些恢复步骤可能被跳过。
可靠的修复应避免程序在异常退出后保留无法安全使用的内部状态,并保证后续执行不会依赖已经失效的对象或数据。
6.2 将危险操作推迟到安全执行点
运行时中的某些操作不适合在任意执行位置完成。例如,错误处理过程中如果立即执行复杂的清理或再次分配内存,可能进一步增加状态管理难度。
因此,修复方案可能涉及延迟处理某些错误状态,并在引擎能够安全处理的执行点完成后续操作。
在阅读补丁时,应以具体版本的源代码为准,确认其实际使用的机制、状态变量和执行时机,而不要仅凭概括性描述推断内部实现。
6.3 增加回归测试
回归测试用于验证漏洞修复没有被后续修改破坏。
对于此类问题,测试至少应关注:
- 异常退出后 PHP 是否能够正确结束当前请求;
- 内存管理器状态是否仍然一致;
- 后续请求是否可以正常处理;
- Session 上传进度等相关功能是否仍能正常工作;
- 修复是否引入新的崩溃、资源泄漏或行为回归。
应优先使用 PHP 官方仓库中的安全公告、补丁提交和测试用例作为依据。
七、如何确认服务器是否受影响
仅检查 PHP 主版本号通常不够。发行版可能会将安全补丁回移到较旧的软件包版本,因此还需要检查具体软件包及其安全更新记录。
7.1 查看 PHP 版本
在服务器上执行:
1 | php -v |
前者查看命令行 PHP 版本,后者显示配置文件加载情况。
如果服务器使用 PHP-FPM,还应检查实际运行的 FPM 版本:
1 | php-fpm -v |
不同发行版的可执行文件名可能不同,例如可能使用带版本后缀的命令。也可以通过服务管理器查看实际运行的进程和服务。
注意:命令行 PHP 与 PHP-FPM 可能不是同一版本或使用不同配置。 对 Web 服务进行排查时,应以实际处理请求的 FPM 实例为准。
7.2 查看运行中的 PHP-FPM 服务
使用 systemd 的系统可以执行:
1 | systemctl --type=service --all | grep -i php |
确认服务名称后,再查看具体服务状态:
1 | systemctl status php-fpm |
如果服务名称不同,应替换为实际名称。
还可以检查正在运行的进程:
1 | ps -ef | grep '[p]hp-fpm' |
7.3 核查官方安全公告
建议依次确认:
- 官方公告列出的受影响版本;
- 官方公告列出的修复版本;
- 当前 PHP 分支是否仍处于安全支持周期;
- 操作系统发行版是否提供回移补丁;
- 当前安装的软件包是否已包含相关修复。
不要直接将上游版本号与发行版的软件包版本进行字符串比较。对于 Debian、Ubuntu、RHEL 等发行版,应结合各自的安全公告和软件包变更记录判断。
八、修复与临时缓解建议
8.1 首选方案:升级到包含修复的版本
优先使用 PHP 官方提供的已修复版本,或操作系统发行版提供的、明确包含相关安全修复的软件包。
升级前建议:
- 确认当前 PHP-FPM 与扩展模块的兼容性;
- 在测试环境验证主要业务;
- 备份配置文件;
- 通过软件包管理器完成升级;
- 重启或重新加载相关服务;
- 确认实际运行的 PHP-FPM 已使用更新后的二进制文件。
对于生产环境,不建议直接从未经验证的第三方来源下载 PHP 二进制文件覆盖现有安装。
8.2 临时关闭 Session 上传进度功能
如果官方公告或补丁分析确认该功能与目标环境的风险相关,并且业务不依赖它,可以评估关闭:
1 | session.upload_progress.enabled = Off |
修改前应确认 PHP-FPM 实际加载的配置文件,并在修改后检查配置是否生效。
该措施属于针对特定功能路径的风险降低措施,不能替代升级补丁,也不能保证消除所有相关漏洞路径。
8.3 限制上传请求
如果业务不需要大文件上传,可以考虑限制请求体大小和上传文件大小:
1 | ; php.ini 示例,数值应按业务需求调整 |
同时,应在 Nginx 或其他前端 Web 服务器上配置请求体大小限制。例如:
1 | client_max_body_size 16m; |
这些限制可以降低异常大请求带来的资源消耗,但不能保证阻止所有能够触发内存错误的请求。
8.4 合理设置 PHP-FPM 工作进程回收策略
PHP-FPM 的 pm.max_requests 用于限制单个工作进程在被重新生成前处理的请求数量。
例如:
1 | pm.max_requests = 500 |
实际值应结合应用的内存使用情况、请求量和性能需求调整。
较小的值可以缩短单个工作进程的存活时间,但会增加进程重建开销。它不能替代正确的请求清理逻辑,也不能保证在两个请求之间发生的状态问题不再出现。
不建议在缺少证据时一律设置为 1,因为这可能显著增加进程创建开销。
8.5 不要为了绕过 OOM 而盲目取消内存限制
PHP 的 memory_limit 用于限制脚本可使用的内存。针对 OOM 问题,盲目提高限制甚至设置为无限制,可能让异常请求消耗更多服务器资源。
例如,以下配置会取消 PHP 脚本的内存限制:
1 | memory_limit = -1 |
除非有明确的业务需求和充分的资源隔离,否则不建议将其作为漏洞缓解措施。对于可能涉及异常内存状态的问题,应优先修复代码路径,而不是让进程能够消耗更多内存。
九、适用于生产环境的排查清单
可以按照以下顺序开展检查:
- 确认实际处理 Web 请求的 PHP-FPM 版本。
- 对照 PHP 官方公告与操作系统发行版安全公告。
- 确认是否已安装包含修复的更新包。
- 检查
session.upload_progress.enabled的实际值。 - 核查 Nginx、反向代理和应用层的请求大小限制。
- 检查 PHP-FPM 工作进程的内存使用和异常退出日志。
- 在测试环境验证升级后的上传、Session 和核心业务流程。
- 升级后确认实际运行的服务已加载新版本。
- 如果怀疑已经遭到利用,保留日志、进程信息和相关证据,并按事件响应流程开展调查。
不要仅凭日志中出现 Allowed memory size exhausted 就认定服务器遭到攻击。该信息也可能由正常业务中的内存使用超限导致,需要结合请求记录、异常频率、进程状态及其他证据综合判断。
十、参考资料
以下链接用于核查漏洞公告、源代码与修复提交。阅读时应优先确认页面中的发布日期、受影响版本和修复版本,并注意不同分支的回移补丁可能存在差异。
- PHP 官方安全公告:GHSA-w8hw-w9fx-j79w
- PHP 官方安全公告:GHSA-6wmf-4rv5-p3rp
- PHP 源码修复提交:506e87fa7dcd
- PHP 8.4 分支相关修复提交:31c0ea7d
- PHP 8.3 分支相关修复提交
- PHP 8.5.12:Zend 内存管理器源码
- PHP 8.5.12:PHP 主运行时源码
- PHP 8.5.12:Session 扩展源码
- PHP 8.5.12:Zend 测试目录
- Wiz:PHP2Shell 与 PHP RCE 相关分析
- CERT-EU 安全公告
总结
CVE-2026-109618 与 CVE-2026-109619 所涉及的关键问题,在于异常退出机制、内存管理状态和长期运行的 PHP-FPM 工作进程之间可能存在复杂的交互。
分析这类漏洞时,需要区分三个层次:
- 触发条件: 哪些输入和运行状态能够进入异常路径。
- 内存安全影响: 异常退出是否导致状态损坏、失效引用或其他内存错误。
- 实际可利用性: 攻击者是否能够在目标环境中进一步控制执行流程。
对服务器管理员而言,最可靠的应对措施仍然是核对官方安全公告、升级至包含修复的软件包,并在升级后验证实际运行的 PHP-FPM 实例。上传大小限制、关闭非必要功能和调整工作进程回收策略可以作为辅助防护,但不应被视为正式补丁的替代品。
