近期,引起全球 PHP 网站安全领域高度关注的,是被称为 PHP2Shell 的两个严重安全漏洞:CVE‑2026‑109618 和 CVE‑2026‑109619。

这两个漏洞均位于 PHP 引擎核心的请求处理与内存管理机制中,CVSS v4.0 评分均为 9.2 分(满分 10 分),属于危急级别。攻击者无需登录或进行身份认证,就有可能通过精心构造的 HTTP 请求触发内存损坏,最终在服务器上执行任意代码(Remote Code Execution,RCE)。
PHP 广泛应用于 WordPress、Drupal、Joomla、MediaWiki 等网站和内容管理系统,因此漏洞的潜在影响范围非常广。漏洞披露后,安全厂商陆续发布风险警报,并发现针对相关漏洞的攻击尝试。Palo Alto Networks、Red Hat、JFrog 等机构均发布了技术分析和修复建议。
这次事件尤其值得关注的地方在于:两个漏洞并非来自某个 PHP 插件或网站应用,而是源于 PHP 引擎自身对异常退出、内存释放和请求状态清理的处理缺陷。它们暴露了一个长期存在于服务器运行时中的安全问题:当一个请求异常终止时,如何确保它留下的内部状态不会污染后续请求?
一、漏洞的根本原因:PHP 请求生命周期中的内存状态管理缺陷
PHP 在处理 HTTP 请求时,会创建大量临时数据结构,包括字符串、数组、会话数据、上传文件元数据和内部对象。这些数据通常分配在当前请求专用的内存区域中,并在请求结束时统一清理。
为了处理严重错误,PHP 历史上使用了 zend_bailout() 等机制,通过 longjmp() 跳出当前执行上下文,终止正在进行的操作。
这种机制可以快速中止发生严重错误的请求,但也带来了一个风险:程序可能在数据结构尚未完全初始化,或者清理工作尚未完成时突然跳出。
如果异常处理代码没有同步更新相关指针和状态标记,后续清理流程就可能访问无效数据,甚至让已经释放的内存继续被其他代码使用。
PHP2Shell 的两个漏洞,分别从不同角度暴露了这一问题。
1. CVE‑2026‑109618:OOM 内存损坏导致远程代码执行
该漏洞对应 PHP 官方安全公告 GHSA‑w8hw‑w9fx‑j79w。
它的根本原因是 PHP 在处理内存耗尽(Out‑of‑Memory,OOM)时,异常退出机制与请求内存初始化、清理流程之间存在不一致。
具体机制如下:
第一步:PHP 开始处理请求并创建内部数据结构。
当服务器接收到 HTTP 请求,尤其是 multipart/form-data 文件上传请求时,PHP 会在应用程序代码执行之前解析请求内容,并创建 HashTable、字符串、上传元数据及会话相关结构。
这些结构可能需要多次动态内存分配才能完成初始化。
第二步:攻击者利用请求内容触发内存分配失败。
攻击者可以构造特殊的请求,使 PHP 在解析请求的过程中消耗大量内存。当内存分配达到 memory_limit 或其他内存限制时,PHP 会触发 OOM 错误。
关键问题在于:部分请求数据结构可能已经被其他内部模块引用,但还没有完成全部初始化。
第三步:longjmp() 导致初始化或清理流程被跳过。
PHP 通过 zend_bailout() 触发异常退出,使用 longjmp() 跳出当前执行上下文。
这意味着原本应该继续执行的初始化、状态回滚或指针清理代码可能被直接跳过。
此时,PHP 内部可能已经存在指向半初始化数据结构的指针,但这些数据结构并不满足正常使用条件。
第四步:请求结束时的清理流程访问无效状态。
请求启动失败或发生异常后,PHP 仍可能进入请求关闭和资源清理流程。
如果清理代码将这些半初始化的数据结构当作有效对象处理,就可能出现未初始化内存读取、错误指针访问、内存损坏等问题。
在特定部署和内存布局条件下,攻击者可以进一步控制堆内存布局,使这些错误访问演变成可利用的内存破坏,最终实现远程代码执行。
这一漏洞最值得警惕的地方是:攻击可能发生在 PHP 应用程序代码执行之前,因此不能仅依靠 WordPress、Drupal 等应用自身的安全更新来解决。
2. CVE‑2026‑109619:陈旧全局状态与跨请求 Use‑After‑Free
该漏洞对应 PHP 官方安全公告 GHSA‑6wmf‑4rv5‑p3rp。
与第一个漏洞主要涉及 OOM 过程中半初始化数据结构不同,第二个漏洞的核心问题是:PHP 在释放当前请求的内存后,没有确保所有长期存在的全局状态都同步清除对这些内存的引用。
这会造成典型的 Use‑After‑Free(UAF,释放后使用)问题。
要理解这一漏洞,需要先了解 PHP Worker 的运行方式。
在 Apache mod_php、PHP‑FPM 等常见部署模式中,服务器通常不会为每个 HTTP 请求重新启动一个 PHP 进程,而是让长期运行的 Worker 连续处理多个请求。
例如:
- Worker 1 处理用户 A 的请求。
- 请求结束后,PHP 释放该请求使用的临时内存。
- 同一个 Worker 随后处理用户 B 的请求。
正常情况下,前一个请求的临时状态应当被彻底清理,后一个请求应从干净的状态开始。
但 CVE‑2026‑109619 所涉及的缺陷,使这一生命周期隔离在特定异常路径下失效。
第一步:请求处理期间,PHP 将临时内存地址保存在长期存在的状态中。
PHP 内部存在一些生命周期可能长于单个请求的变量和状态,例如全局指针、错误处理信息、异常回溯信息以及 OPcache 相关状态。
正常情况下,这些状态在使用请求内存时,需要确保其引用关系与请求生命周期一致。
问题在于,某些执行路径可能把属于当前请求的内存地址保存在这些长期存在的状态中。
换句话说,一个本应随着请求结束而失效的地址,被保存在了可能跨越多个请求的内部变量中。
第二步:请求发生致命错误,清理过程被异常打断。
当 PHP 遇到 OOM 或其他致命错误时,zend_bailout() 可能通过 longjmp() 直接跳出当前执行上下文。
如果跳出发生在指针清理之前,PHP 就可能没有机会将长期状态中的相关指针设置为 NULL,或者将其重置到安全状态。
此时,请求内存虽然随后被释放,但全局状态仍然保留着原来的内存地址。
这就是所谓的陈旧指针(Stale Pointer)。
第三步:内存释放后,原地址不再属于原来的数据结构。
请求结束时,PHP 会释放该请求的内存区域。
但需要注意,释放内存并不意味着内存中的旧数据会立即被清零,也不意味着所有指向该地址的指针会自动失效。
因此,长期状态中的指针仍可能指向一块已经释放的内存。
从程序生命周期角度看,这时已经形成了 Use‑After‑Free 条件。
第四步:同一个 Worker 处理新请求时,可能再次访问陈旧指针。
当 Worker 接收到下一个 HTTP 请求,PHP 会重新初始化部分请求状态。
然而,如果某些全局状态没有在前一个请求结束时彻底重置,新请求就可能在相关状态被正确初始化之前,访问上一个请求留下的陈旧指针。
更严重的是,新请求的内存分配可能重新使用之前释放的内存地址。
这样,原本指向旧请求数据的指针,可能在新请求中指向已经被其他数据占用的内存区域。
如果攻击者能够影响这些新分配的数据,就可能间接控制原本应该只由 PHP 内部代码读取的状态。
第五步:利用内存重用和状态混淆实现代码执行。
在研究人员分析的攻击路径中,PHP 的 Session 上传进度处理机制是一个重要入口。
PHP 可以在处理 multipart/form-data 上传请求时,记录上传进度,并将相关信息写入 Session 文件。
当后续请求再次读取该 Session 时,PHP 会对其中的序列化数据进行解析。
如果此前异常退出留下的内部指针仍然有效可见,而新请求又重新使用了相应内存区域,就可能使攻击者控制的数据进入原本不应该被信任的内部处理流程。
在特定利用链中,这种状态混乱可以进一步影响对象反序列化、对象序列化回写等操作,最终劫持程序控制流,实现任意代码执行。
JFrog 的技术分析指出,攻击链涉及 Session 数据反序列化、内部 zval 及其生命周期管理结构,以及异常退出后未正确清理的指针。研究人员还分析了 PHP 8.5 中错误回溯状态和 OPcache 共享内存状态等其他长期状态路径。
因此,CVE‑2026‑109619 并不能简单理解为“上一个请求的数据泄露给下一个请求”。它的真正危险在于:请求级内存已经释放,但跨请求的内部状态仍然保留着对旧内存的引用;当后续请求重新利用这些内存时,攻击者就可能影响 PHP 引擎自身的执行过程。
3. 两个漏洞之间的关系
这两个漏洞具有相似的底层问题,但并不是完全相同的漏洞。
| 比较维度 | CVE‑2026‑109618 | CVE‑2026‑109619 |
| 核心问题 | OOM 导致请求状态未完成初始化 | 异常退出后长期状态保留陈旧指针 |
| 主要内存问题 | 半初始化数据结构被错误使用 | 已释放内存仍被后续代码访问 |
| 关键机制 | longjmp() 中断初始化和清理 | 跨请求指针及状态未正确重置 |
| 主要风险 | 内存损坏、未初始化内存访问 | Use‑After‑Free、状态混淆 |
| 攻击特点 | 可在请求启动阶段触发 | 某些利用路径需要同一 Worker 处理连续请求 |
两者共同揭示了 PHP 引擎在异常处理和请求生命周期管理方面的深层缺陷。虽然它们可以在某些攻击链中相互关联,但不能认为任何环境下都必须同时利用两个漏洞才能实现 RCE。
二、为什么这两个漏洞特别危险?
这次漏洞的危险性主要来自三个方面。
首先,漏洞位于 PHP 引擎底层。
攻击者不需要寻找某个特定 WordPress 插件或 CMS 的代码缺陷。只要 PHP 请求处理路径满足相关条件,就可能受到影响。
其次,攻击可能不依赖用户身份认证。
攻击者可以从网络侧构造请求,触发 PHP 内部异常路径。特定部署模式下,甚至可能在网站应用代码运行之前完成攻击。
第三,长期运行的 Worker 放大了跨请求状态问题。
在传统 Web 服务中,一个 Worker 连续处理大量请求是常见架构。如果前一个请求留下的陈旧状态没有清理干净,后续请求就可能受到影响。
这说明,服务器运行时的内存安全不仅取决于单个请求是否正确处理,还取决于不同请求之间是否真正实现了状态隔离。
三、PHP 官方如何修复?
PHP 官方于 2026 年 10 月 21 日发布了安全更新,修复版本包括:
- PHP 8.5.12
- PHP 8.4.27
- PHP 8.3.36
- PHP 8.2.35
此次修复不仅针对单一内存访问位置,而是从异常退出和请求状态管理两个方面进行调整。
1. 改变 OOM 处理方式。
对于 CVE‑2026‑109618,PHP 调整了软 OOM 的处理机制,不再允许内存分配失败时通过 longjmp() 在关键操作中途直接跳出。
新的机制将软 OOM 延迟到后续虚拟机安全点处理,使当前操作有机会完成必要的清理工作。
当内存消耗达到更高的硬性阈值时,PHP 则通过 _exit() 直接终止进程,避免继续在不一致的内存状态下运行。
2. 强化长期状态清理。
对于 CVE‑2026‑109619,PHP 加强了请求关闭阶段的状态重置,确保错误回溯信息、Session、OPcache 及其他长期存在的内部状态,不会继续保留指向已经释放的请求内存的指针。
3. 限制上传进度解析期间的对象行为。
PHP 还调整了 Session 上传进度解析过程中的对象处理方式,避免在这一特殊解析路径中触发不必要的自动加载和析构函数调用,从而关闭相关对象注入利用路径。
这些修改体现了一个重要原则:异常退出不仅要终止当前操作,还必须保证所有跨越当前操作生命周期的状态都被正确重置。
四、网站管理员应该如何处理?
1. 检查 PHP 运行版本。
在服务器上运行:
php -v
也可以通过网站临时创建 phpinfo.php 检查 Web 环境实际使用的 PHP 版本。需要注意,命令行 PHP 和 PHP‑FPM 使用的版本可能不同。
2. 尽快升级到修复版本。
建议升级至:
- PHP 8.5.12 或更高版本;
- PHP 8.4.27 或更高版本;
- PHP 8.3.36 或更高版本;
- PHP 8.2.35 或更高版本。
如果使用云主机或共享主机,应联系服务商确认 PHP 运行环境是否已经更新。
如果自行管理服务器,应通过操作系统的软件包管理器或 PHP 官方渠道安装修复版本,并重启 PHP‑FPM、Apache 或相关 Worker。
3. 旧版本需要特别处理。
PHP 8.1 及更早版本已经结束常规官方支持,不能简单假设它们会获得与 PHP 8.2 以上分支相同的版本更新。
不过,PHP 项目已提供部分旧分支的安全补丁源码,部分 Linux 发行版和商业维护服务商也可能提供回移补丁(Backport)。
因此,管理员应核实实际安装的软件包是否包含安全修复,而不能只看版本号。无法获得安全更新的环境,应优先迁移至受支持版本。
4. 临时缓解措施不能替代升级。
在无法立即更新的情况下,可以根据 PHP 官方公告及服务商建议采取临时措施,例如:
- 关闭
session.upload_progress.enabled,减少相关上传进度处理路径的暴露; - 根据具体版本和运行环境调整致命错误回溯功能;
- 检查内存限制和请求大小配置,降低攻击者触发异常路径的机会;
- 对公网暴露的 PHP 管理接口和服务增加访问限制;
- 使用 Cloudflare WAF、ModSecurity 等安全设备过滤异常请求。
但这些措施只能降低部分攻击风险,不能保证彻底消除漏洞。某些配置还可能影响正常文件上传、错误诊断或服务器资源保护。
5. 升级后检查是否已经遭到入侵。
漏洞披露前后都可能存在攻击尝试,因此升级并不意味着可以忽略此前的安全风险。
建议检查:
- Web Server 和 PHP‑FPM 日志中是否存在异常请求;
- 是否出现 Web Worker 启动 Shell、下载程序或异常子进程的行为;
- 网站目录是否出现不明 PHP 文件、Webshell 或其他后门;
- 是否存在异常计划任务、系统服务和账号;
- 是否有异常外联、CPU 使用率或网络流量。
如果有证据表明服务器已经被远程执行代码,应按照安全事件响应流程隔离主机、保留证据、检查凭据,并在确认清除风险后恢复业务。
五、总结:真正的问题是异常状态如何跨越请求生命周期
PHP2Shell 的两个漏洞再次说明,软件安全不仅取决于正常执行路径是否正确,也取决于程序在异常情况下能否保持状态一致。
CVE‑2026‑109618 暴露的是 OOM 异常退出导致的半初始化状态问题;CVE‑2026‑109619 暴露的是请求结束后陈旧指针仍然存活、并可能影响后续请求的问题。
从更广泛的软件工程角度看,这两个漏洞涉及三个关键原则:
- 资源生命周期必须一致。 内存被释放后,所有指向它的引用都必须失效或被安全重置。
- 异常退出必须保证状态完整。 不能只终止当前执行过程,还必须处理已经建立的内部状态和资源关系。
- 不同请求之间必须实现可靠隔离。 长期运行的 Worker 不能让前一个请求的异常状态污染后续请求。
这次事件的影响不仅限于 PHP。对于任何长期运行、重复处理任务并复用内存的服务器程序而言,跨任务状态污染、异常退出和 Use‑After‑Free 都是必须认真对待的安全问题。
对于 PHP 网站管理员,目前最重要的行动仍然是确认运行环境是否已经包含安全补丁,并及时升级。
参考资料
- PHP2Shell 两个漏洞的技术分析:JFrog Security Research,2026 年 10 月 21 日。
- Palo Alto Networks Unit 42:PHP2Shell 威胁简报,2026 年 10 月 29 日更新。
- Red Hat:RHSB‑2026‑013,PHP 请求处理内存损坏安全公告。
- Elastic Security Labs:PHP2Shell 漏洞检测与攻击行为分析。
- PHP 官方安全公告:GHSA‑w8hw‑w9fx‑j79w(CVE‑2026‑109618)及 GHSA‑6wmf‑4rv5‑p3rp(CVE‑2026‑109619)。
说明:漏洞利用的具体可行性与 PHP 版本、部署方式、配置和请求处理路径有关。本文描述的是公开研究披露的漏洞机制,不代表所有 PHP 部署环境都具有相同的利用条件。
▶️ 喜欢这篇内容?创作真的很辛苦!
欢迎关注我们的 YouTube 频道:
AI Rosetta
你的点赞、订阅和分享,是对我们最大的支持!🙏
更多AI新闻请访问:
https://www.airosetta.com