PHP2Shell 发现者复盘了,这个 9.2 分的严重安全漏洞,在 PHP 里藏了 22 年!
昨天全世界都被 php2shell 的安全漏洞搅得天翻地覆,我也写了两篇文章:
第一篇是在补丁发布之前,介绍这两个漏洞,并提醒大家注意的:
PHP 爆发近几年最严重的安全漏洞「php2shell」,9.2 分,能直接拿下服务器权限,WordPress 站点都要注意了!
第二篇是补丁发布之后,让大家尽快更新的:
补丁来了!PHP 9.2 分严重漏洞「php2shell」今晚修好,所有版本都要升,赶快更新!
大家在忙着连夜打补丁、疯狂围观的同时,可能也最好奇:这个足以撼动半个互联网生态的底层远程代码执行漏洞,到底是怎么被挖出来的?
正好,安全研究机构 Calif Research 最近放出了详细的技术复盘与漏洞传奇全纪录。有兴趣的可以去看英文:
2004:一切的起点
故事要从 2004 年说起,那时还没 iPhone,PHP 安全圈的大神 Stefan Esser 还只是 PHP 安全专家,今天的安全圈习惯叫他 Hardened-PHP 项目创始人,或者干脆叫大神。

尽管还没有大神的称号,但是 Stefan 在 2004 年就挖掘出了 PHP 历史上第一个内存限制相关的远程漏洞,该漏洞的危害级别极高,以至于拒绝向公众公开 Exploit(漏洞利用程序)。除了 CORE 和 Immunity Inc 这两家商业漏洞公司各自私下掌握着非公开的利用方式之外,当时市面上能看到的,只有 IRC 上流传的那些假 Exploit,专门等着反打运行它的人的机器。
他后来更出名的那些工作,2008 年的按引用传递 / 中断漏洞,以及更著名的 unserialize() 释放后使用和 POP 对象注入漏洞,其实都比 2004 年这个要晚。随着时间的推移,PHP 中所有默认不可序列化的类里存在的释放后使用或类型混淆漏洞,都已被发现并修复。
2020:被忽视的 OOM(内存溢出)
转眼到了 2020 年,安全研究员 jr@coredu.mp 向 PHP 官方提交了一个看似无害的 Bug #78876,指出某些超长变量可能导致 OOM(Out-of-Memory,内存溢出),并且无法清理临时文件。
但在当时,这个报告并没有引起多大的重视。

2024:安全审计的重现
快进到 2024 年,开源安全基金会 OSTIF 与安全机构 Quarkslab 对 PHP 源码进行了联合安全审计,并重新发现了当年的 OOM 问题。
然而,由于当时未能证明该漏洞可被实际利用,因此该 Bug 并未被修复。

2026年7月30日:「修补地球」行动
时间来到 2026 年,OpenAI 邀请安全机构 Calif Research 加入了其发起的 “Patch the Planet”(修补地球) 计划,而 PHP 成为了他们锁定的高影响力高危目标。
为了让 PHP 官方基金会深度参与,安全专家 Stefan Esser 亲自牵线搭桥,把这支队伍介绍给了 PHP 基金会生态安全团队的 AI 安全工程师 Volker Dusch。Volker 接受了邀请,随后针对 PHP 源码的这场挖掘正式拉开帷幕。
带队的人是安全机构 Calif Research 的研究员 Ryan Austin,他就是这两个漏洞的第一发现者,也是这篇技术复盘的作者,后面行文中简称 Ryan。

这里简单介绍一下「修补地球」这个计划,它是 OpenAI 发起的一项开源安全与漏洞协同治理计划,做法是邀请顶尖的安全研究机构与专家,对全球关键的开源基础设施做深度漏洞挖掘与安全加固,走「AI 辅助审计 + 人工协同修复」的路子,赶在黑客动手之前把高危漏洞补掉。
OpenAI 公告里点名的第一批项目是这些:cURL、NATS Server、pyca/cryptography、Sigstore、aiohttp、Go、freenginx、Python 和 python.org。整个计划的执行方是安全公司 Trail of Bits,据 OpenAI 说法,对方把整个安全研究团队都投了进来。
2026年8月1日:重大突破
项目启动仅几天后,结合 Stefan 早年研究留下的线索,研究团队就成功挖出了第一个 OOM-Fatal(内存溢出致命漏洞),并拼凑出了针对 WordPress 实施远程代码执行(RCE)所需的三个关键中断点之一。
既然第一个 Bug 已经突破,团队乘胜追击,又找到另外两个,最终锁定了两个能够对 mod_php 和 PHP-FPM 实现稳定利用的漏洞变体。

2026年10月21日:尘埃落定
经过与 PHP 官方的紧密配合,官方最终在 2026 年 10 月 21 日正式发布了安全补丁。

最早能追到 PHP 5.4.0
这个漏洞到底是怎么造成的?
说白了就一句话:PHP 处理每个请求,都要先在自己内存里把一堆东西准备好。如果准备工作干到一半被强行打断,那些半成品不会被认真收拾,下一个请求接着在同一块地方干活的时候,会把半成品当成成品继续用。
什么时候会被打断?Ryan 给的条件有两个:一是某次内存分配就要超出 memory_limit 的上限,二是另一个致命错误(Fatal Error)在 PHP 做完正常清理之前把它打断了。
有了这两样,就能做到内存泄露和远程代码执行。Ryan 一共找到三个这样的可被打断的位置,外加两个能把 PHP 逼到内存耗尽的办法。
最早受影响的代码能追到 PHP 5.4.0。具体能走通哪几条路径,取决于 PHP 版本和运行方式,但在默认配置下始终可利用,一个参数都不用改。
下面是他确认能够免登录实现远程代码执行的应用清单,某些情况下还能直接针对任意 PHP 脚本:
- WordPress 7.1.2
- Drupal 11.4.3
- Joomla 6.1.4
- MediaWiki 1.46.2
- Roundcube 1.7.4
- Magento 2.4.9
- phpMyAdmin 5.2.3
- Nextcloud 35.0.1
- Laravel 13.11
- Symfony 8.1
- WHMCS
php2shell 攻击大概是怎么打的
第一步,想办法让 PHP 在想要的那一刻崩掉。
他的办法很巧:往服务器发一大批「空壳」的上传记录。这种记录不带任何文件内容,按 PHP 的规则也不计入上传数量限制,但 PHP 照样会给每一条都把配套信息从头建一遍。
几百条这样的空记录,就能吃掉默认 128 MiB 的 PHP 堆,而它们整个 HTTP 请求体还留在默认的 8 MiB 限制以内,门卫那边看不出任何异常。内存一到底 PHP 就会报错退出,而报错的时机掐得准不准,就是整件事的关键。
第二步,找到那些被打断之后会留下烂摊子的位置。
他一共找到三个,其中两个只在 Apache 加 mod_php 这种组合上成立。
第三步,用下一个请求把这份没收拾干净的东西捡走。
同一个工作进程接着服务下一个请求,那份还指向已释放内存的状态就会被当成正常数据用。攻击者借这一点控制住 PHP 往哪儿放指针、放什么类型的数据,最后走到远程代码执行。
原文在第一步和第三步各附了一段 exploit 跑通的演示视频。大家也可以去原文看看:
顺带说一句,他中间还干了一件挺值得抄的事:把这套「先登记状态 → 再去做一件可能失败的事 → 最后才初始化或者回滚」的套路抽象成一个模式,让模型拿着它回去重新扫了一遍代码库,结果在会话上传进度这个功能里又找到了新问题。这也说明挖到的不是一次性的偶然。
mod_php 和 PHP-FPM 完全不一样
这是整件事里对站长最有用的一条。
PHP-FPM 一旦在请求启动阶段崩掉,就直接把这个工作进程扔掉重新拉一个,那堆没收拾干净的东西跟着一起没了;mod_php 不扔,下一个请求继续用这个进程。
所以前面那两条路径,跑 FPM 的站点直接免疫。
只有第三条跟应用有关的路径两边都成立。差别在崩的时刻:启动成功之后、脚本执行期间崩掉,FPM 的工作进程还能活着。
这条路径的关键是应用自己得有一个「启动之后的解码操作」。Ryan 给的判定条件是,只要有可达的这类操作就会暴露,比如 session_start()、session_decode() 或者 unserialize()。
WordPress 就是通过一次再普通不过的 maybe_unserialize() 调用踩上去的,phpMyAdmin 则是通过一次普通的 session_start()。
而且他还特别强调了一句:被解码的数据本身可以是完全合法的。 攻击者只要控制得住周围的内存压力,就能在有用的位置打断这次解码,再用稍后的上传进度请求,在活着的工作进程里消费这份陈旧状态。
所以跑 FPM 的处境要好一大批,但也不是完全没事。
一条二十二年的时间线
查完 PHP 这些漏洞之后,Ryan 还顺手去看了看 Hack/HHVM 是不是也受影响。结果歪打正着,找到了整件事最关键的那一环。
他发现上游 PHP 在 2020 年有个补丁,对应的正是 #78876 那个「multipart 里长变量导致 OOM 和临时文件没清理」的问题。
有意思的是,他去看这个 2020 年的补丁的时候,注意到 git 里挂着一枚写着 security-audit-2024 的标签。 这枚标签指向 2024 年的 OSTIF 审计也查到了类似结果。他找 PHP 那边核实,得到的答复确认了:2024 年确实发现过一个相似或相关的漏洞,但当时利用还没被证实。
所以从第一发现者的视角,这条时间线长这样:
| 时间 | 事项 |
|---|---|
| 2004 年 | Stefan Esser 发现第一个 PHP 内存限制远程漏洞 |
| 2020 年 | jr@coredu.mp 报告 #78876 |
| 2024 年 | OSTIF 与 Quarkslab 审计发现与 #78876 相关的漏洞,但利用未获证实 |
| 2026 年 8 月 1 日 | Calif 挖出多条各自独立、都能走到 RCE 的路径,把 2024 年那批问题连同新的触发点一起炸活 |
| 2026 年 8 月 5 日 | Calif 研究员 Ryan Austin 在 X 上发布其中一个发现的哈希值,留时间戳 |
| 2026 年 8 月 11 日 | 向 PHP 报告第一个 bug,协调披露正式启动 |
| 2026 年 8 月 14 日 | 把可用的攻击代码(PoC)交给 PHP |
| 2026 年 8 月 15 日 | PHP 确认这份 PoC 可以跑通 |
| 2026 年 8 月 21 日 | 向 PHP 报告第二个 bug |
| 2026 年 10 月 21 日 | 修复版本发布 |
漏洞是如何找到的?
关于这次发现里 AI 到底起了多大作用,文章里专门回答了,几条写得很直白。
第一,工具上没有特殊之处。
最初的漏洞发现过程中,没有使用任何特殊的测试框架(harness),也没有使用任何不是所有人都拿得到的模型。
第二,他下手的理由全是人的东西。
我挑的是自己有背景的领域,非常熟悉它的漏洞历史,而且心里有个假设:这个代码库里潜伏着某一类特定的 bug。
顺着这一点,他给了全文最重要的一句判断:
即便在模型主导的这个时代,「了解你的目标」(know your target)依然一如既往地适用。至少就目前而言,没有任何模型能仅靠一句简单的提示就把「所有漏洞」挖出来。
第三,模型默认不会往老线索上走。
模型确实可能吞下过公开的历史漏洞数据,但在默认的变体分析(variant analysis)里,它们往往是朝着最新、最显眼的那些漏洞,向附近的老代码走。它们并不会可靠地从一个模糊的、二十年前的利用原语开始。
所以他给的建议落得很实:把自己对某个目标漏洞历史的那份目录留着,才能明确地提示它往那个方向走。
总结与安全建议
首要建议:立即升级打补丁。请尽快将 PHP 升级到官方的安全修复版本:
补丁来了!PHP 9.2 分严重漏洞「php2shell」今晚修好,所有版本都要升,赶快更新!
临时缓解措施(无法立即升级时的权宜之计,不能替代补丁):
- 对于 PHP-FPM:关闭 Session 上传进度(设置
session.upload_progress.enabled=0)并将memory_limit设置为-1。 - 对于 mod_php:将
memory_limit设置为-1(注意:这会取消 PHP 内存上限,增大内存耗尽的风险)。
不过 Ryan 自己立刻补了一句警告:去掉内存上限等于撤掉 PHP 的内存闸门,内存耗尽的风险反而更高。 这两条都只是争取一点时间,真正的解法只有打补丁。
所以,去打补丁吧!!!
最后还是建议大家去看看英文,写的很详细,有很多技术细节,特别是对安全感兴趣的同学: