
如果你手头刚好维护着几台跑着 PHP 的服务器,那刚刚过去的 2026 年 10 月 20 日和 21 日,你大概率也没怎么睡好。
在过去这 48 小时里,整个互联网像是演了一出荒诞的黑色幽默剧:欧洲多国政府网站主动拔网线关站、维基百科的后台系统(Phabricator)和 4chan 挂出停机公告、日本首相高市早苗罕见地在 X(原 Twitter)上发帖呼吁全国企业“来不及打补丁就先停服”,甚至还有一款运营多年的日本网页游戏《World Quest》因为老架构实在没法修补,当天直接宣布原地永久关服。
引发这一切的,是两个潜伏在 PHP 核心引擎里长达 14 年的未授权远程代码执行(RCE)漏洞——CVE-2026-109618 和 CVE-2026-109619,外媒和安全圈给它们起了个很顺口的名字:PHP2Shell。
更有意思的是,这本该是一场载入教科书的“AI 辅助安全协同披露”:安全人员用最新的 AI 模型挖出了陈年老洞,提前两个月报给官方,大家憋着大招改了 400 多个文件,约好在 10 月 22 日体体面面地同步发补丁。结果临门一脚,因为一连串让人啼笑皆非的泄密、手滑、误导和供应链背刺,硬生生把一场有序更新变成了一场全球运维的“通宵大逃杀”。
趁着补丁已经落地、服务器终于消停下来,我想把这几天的完整时间线、里面的几个离谱乌龙,以及我去翻了 github.com/php/php-src 的源码提交后看到的底层改动,完完整整地梳理一遍。
一、 暴风雨前的伏笔:一个被搁置了两年的隐患
在讲 10 月 20 日那晚的乱象之前,得先把日历往回翻一翻。很多人看了新闻标题以为这是“AI 突然凭空发现的漏洞”,其实只对了一半。
这两个漏洞的根子,最早可以追溯到 2012 年发布的 PHP 5.4(其中涉及哈希表插入的优化则在 PHP 7.3 引入)。长久以来,C 语言写的 PHP 底层在处理“内存耗尽(Out-Of-Memory, OOM)”时有一个很粗暴的习惯:一旦某个请求申请内存超过了 memory_limit,引擎就会调用 zend_bailout()(底层其实就是 C 语言的 longjmp()),直接跨越无数层函数调用栈,强行跳回顶层去结束当前请求。
早在 2024 年,由德国主权技术局资助、安全机构 Quarkslab 对 PHP 进行代码审计时,就有 PHP 核心开发者注意到了这种“半路强行跳伞”会导致内存状态不一致。但为什么当时没修?
PHP 基金会在 10 月 21 日发布的复盘长文里非常坦诚地承认:这是一次结构性的失败(structural failure)。一方面,当时大家都觉得“虽然内存会乱,但在真实环境里想把它变成稳定的远程代码执行(RCE)几乎不可能”;另一方面,PHP 安全团队当时采用的是“全员共识制”,几套修复方案各有痛点(有的破坏兼容性,有的怕影响性能),大家在邮件列表里吵来吵去谁也说服不了谁,再加上整个基金会全职加兼职一共才 13 个人,这件事就这么被搁置了整整两年。
直到 2026 年 8 月,窗户纸被 AI 捅破了:
- 8 月 1 日至 11 日:参与 OpenAI “Patch the Planet” 计划的安全公司 Calif.io 研究员 Ryan Austin,配合
GPT-5.6 Sol模型对 PHP 源码做深度推理,不仅找到了漏洞,还直接搓出了在默认配置下无需任何前置条件的完整 RCE 利用链,并于 8 月 11 日提交给 PHP 官方;8 月 21 日又提交了第二个漏洞。 - 8 月 19 日至 24 日:另一边,Wiz 的安全团队借助内部平台 Wiz Atlas,配合当时尚未公开发布的 Google 前沿内测模型
Gemini 4 Argon与 OpenAIGPT-5.6 Sol,也独立挖出了这两个洞并上报。
当完整的 Exploit 摆在面前时,“太难利用所以不用修”的幻想彻底破灭。PHP 核心团队的几位大佬(Arnaud Le Blanc、Ilija Tovilo、Jakub Zelenka、Niels Dossche、Nicolas Grekas)全员脱产,连轴转了两个月,几乎把 PHP 的内存超限处理机制重做了一遍,并将全球协同解禁时间(Embargo)定在了 2026 年 10 月 22 日(周四)14:00 UTC。

如果不出现意外,我们本该在周四晚上优雅地敲一行 apt upgrade。但意外偏偏在周二晚上来了。
二、 惊魂 48 小时与“五大连环乌龙”
这场风波之所以搞得人仰马翻,是因为从 10 月 20 日下午开始,几乎每隔几个小时就会爆出一个新的乌龙。
乌龙一:一句 "source: trust me, bro" 引发的泄密与“PHP 6 复活”
时间:10月20日 15:00 — 15:20 UTC(北京时间 23:00)
一切的导火索,是 Hacker News 用户 petecooper 发的一个帖子:《Tell HN: 2x PHP RCE CVEs with CVSS 10.0 Incoming》。
帖子里直接甩出了两个当时还处于未公开状态的编号——CVE-2026-109618 和 CVE-2026-109619,声称这是两个满分 CVSS 10.0 的史诗级漏洞,将在 10 月 22 日公布,最后还调侃了一句消息来源是 "source: trust me, bro"(信我,兄弟)。
事后来看,这个爆料帖本身就是个半吊子乌龙:
- 发帖人自己事后解释,他发帖前特意问了线人“这属于协同披露的保密内容吗?”,线人骗他说不是,他才发出来的。
- 帖子里爆出的关键信息错了一大半:CVSS 分数根本不是 10.0,官方评定是 9.2;发布时间也搞错了。
- 更离谱的是,截图传到 X 和各大安全群后,经过传话筒效应,变成了**“该漏洞通杀 PHP 5、6、7、8 所有版本”——熟悉 PHP 历史的老程序员看到这儿估计都气笑了:PHP 6 胎死腹中十几年、从来就没正式发布过,连不存在的版本都能被你们通杀?**
乌龙二:安全厂商公开“实锤” + 原发现方 Calif 史诗级手滑
时间:10月20日 19:30 — 20:00 UTC(北京时间 21日凌晨 03:30 — 04:00)
原本 HN 上的匿名帖没有任何技术细节,删帖后大家也就当个传闻看。结果安全公司 watchTowr 沉不住气了,其 CEO 直接在 X 上公开发文,表示“我们已经在实验室里复现了,漏洞千真万确”,还顺带抱怨了一句 “我们本来都说好要等到周四上午 11 点的”。这一嗓子直接把小道消息变成了铁证。
眼看捂不住了,PHP 官方安全团队被迫在 19:30 UTC 左右,在 php.net 首页挂出了一则只有 7 行字的紧急预告(这也是 PHP 自 2012 年以来第一次发事前预告),宣布将补丁发布时间从周四提前一天到周三(10月21日 14:00 UTC)。
结果 PHP 官方刚发完预告半小时(20:00 UTC),全场最致命的乌龙出现了:
漏洞的原始发现方之一 Calif.io 团队(不知道是内部误以为官方发通知就等于全面解禁,还是小编配错了定时发布),失误把内部录制的 php2shell 攻击演示视频直接公开发了出来!
虽然 Calif 发现闯祸后赶紧删帖,但视频早已被安全研究员 Kirill Firsov (@k_firsov) 下载并转发到了 X 上,配文:"php2shell. Tomorrow is a helluva day."
这段只有几十秒的视频,成了全世界黑客的“高清寻宝图”——画面里的终端日志把攻击目标(Apache mod_php prefork)、耗时(22.6 秒拿到 www-data 权限)以及核心内存符号(main_heap、stale_reference、zval_ptr_dtor、zend_call_function)漏了个底朝天。
紧接着在 20:33 UTC,欧盟 CERT-EU 发布紧急公告,直接给出了让所有人心跳加速的建议:如果无法立即打补丁,建议把公网上的 PHP 服务直接断网下线。

乌龙三:野生研究员抢发“假 PoC”,带偏全网缓解方案
时间:10月21日 02:00 UTC(北京时间 21日 10:00)
有了泄露视频里的关键词,各路野生安全研究员开始连夜翻 PHP 源码抢热度。其中一位叫 J4ck3LSyN 的研究员在个人博客上高调放出了所谓的“漏洞复现与 PoC”,信誓旦旦地宣称两个漏洞全出在 ext/session/session.c 的文件上传进度功能(session.upload_progress)上。
这篇漏洞百出的文章,直接把全球运维带进了两个巨大的坑里:
- 第一坑:以为关掉
session.upload_progress.enabled = 0就万事大吉。 当天上午,几乎所有安全群都在转发这行配置。直到晚上官方公告出来大家才傻眼:这行配置只能堵住第二个漏洞(CVE-2026-109619)的其中一条路径,对第一个漏洞(CVE-2026-109618,OOM 内存破坏)半点用都没有! 更要命的是,很多开发者图省事,在 PHP 业务代码开头写了一句ini_set('session.upload_progress.enabled', '0')。但上传进度的解析发生在 PHP 引擎启动阶段(脚本还没开始跑呢),这时候调ini_set()返回值是false,根本改不动! 最黑色幽默的是什么?事后 PHP 安全团队成员 Alexandre Daubois 发文透露:在没打补丁前,针对第一个 OOM 漏洞最有效的临时保命设置,其实是把memory_limit设成-1(也就是不限制内存,不让 PHP 触发 OOM 跳伞)! 这跟当天网上教大家“把内存限制调小一点防攻击”的野路子建议,完完全全是反着来的。 - 第二坑:以为开启“网站维护模式(Maintenance Mode)”就能防住。
很多用 WordPress 或 Laravel 的站长第一时间在后台打开了“维护模式”插件。但实际上,这两个漏洞是在 PHP 引擎解析 HTTP
multipart/form-data请求体的时候触发的,此时连你的index.php第一行代码都还没执行。只要请求能进到 PHP 进程里,你挂着再漂亮的维护页面,底层照样被拿 Shell。
顺便一提,PHP 官方事后审查了补丁发布前网上流传的所有野生 PoC(包括 J4ck3LSyN 那份),发现没有一份是真正能跑通完整利用链的。但恐慌已经彻底蔓延开来。
乌龙四:猪队友 Chainguard 提前 6 小时背刺,55 分钟后引来真黑客
时间:10月21日 07:41 UTC(北京时间 21日 15:41)
如果说前面的手滑和瞎猜还只是添乱,那容器安全厂商 Chainguard 干的事就纯属“背刺”了。
为了让各大 Linux 发行版能在 10 月 21 日 14:00 UTC 准时放出二进制更新包,PHP 团队提前把补丁源码同步给了下游厂商。结果在距离约定解禁时间还有 6 小时 19 分钟 的 07:41 UTC,Chainguard 单方面打破保密禁令(Embargo),把打好补丁的 PHP 容器镜像公开发布了出来!
Chainguard 的 CEO Dan Lorenc 后来在 Hacker News 上辩解说:“因为我们看到网上已经有在野利用了,所以我决定提前把修好的免费镜像放出来保护大家。” 但这番说辞遭到了 PHP 基金会的点名怒斥:当时网上的野生 PoC 根本打不通,而 Chainguard 提前 6 小时把修好的二进制镜像放出来,等于是在全球 99% 的普通用户还拿不到补丁的真空期里,直接把“标准答案”塞进黑客手里去做二进制比对(Binary Diff)逆向!
数据不会撒谎:据安全机构 ELLIO 的蜜罐网络监测,就在 Chainguard 放出镜像仅仅 55 分钟后(08:36 UTC),全网就捕获到了第一波真正打中目标要害的在野攻击流量(甚至已经挂上了专门针对 64 位服务器编译的 Mirai 变种加载器)。

乌龙五:其实 php-src 仓库里的补丁早就躺在那儿了?
还有一个让不少安全研究员哭笑不得的细节:有人在 10 月 21 日去翻 github.com/php/php-src 的提交历史时发现,那笔修复这两个漏洞的核心 Commit(作者 Arnaud Le Blanc),其时间戳赫然写着 2026-10-18 15:26:39 UTC。一些提前拉取了源码树或者盯着分支动态的人惊讶地发现,里面整整齐齐躺着将近 200 个新写的 oom_*.phpt 测试用例,几乎把所有可能出问题的内存位置都标出来了——真是应了那句老话:“git log 才是最快的安全公告,其他都是场外解说。”
三、 剥开流言看本质:这两个漏洞到底是怎么打穿 PHP 的?
看完了热闹,我们静下心来看看技术原理。为什么这两个漏洞能让见惯了大风大浪的 NSA 和各国 CERT 都建议“直接拔网线”?
要理解它们,只需要记住 PHP 最核心的工作模式:PHP 的单个请求是无状态的,但处理请求的 Worker 进程(比如 Apache 的 mod_php 子进程,或者 PHP-FPM 的 worker 进程)是长生不老、反复接客的。
在正常情况下,请求 A 进来,PHP 划一块请求专属内存(Request Heap);请求 A 结束,PHP 把这块内存清空释放,再把全局状态复位,然后干干净净地去接请求 B。
而 PHP2Shell 的核心逻辑,就是用两个连续的请求(官方称之为 Producer 生产者请求 和 Consumer 消费者请求)打穿同一进程的隔离墙:

1. CVE-2026-109618:结构体才建了一半,你直接拉电闸?(RCE via OOM Corruption)
这个漏洞出在 PHP 处理内存耗尽(OOM)的 longjmp()(即 zend_bailout)机制上。
想象一下,你在往 PHP 的哈希表(HashTable,也就是 PHP 里无处不在的数组底层结构)里塞数据。在 Zend/zend_hash.c 的 _zend_hash_str_add_or_update_i 函数里(这是 PHP 7.3 的 commit 6de54703f0 引入的一项性能优化),代码是按这个顺序干活的:
- 先把哈希表的元素计数器加 1,修改活跃表的元数据(此时这个新槽位对外已经处于“可见/可访问”状态);
- 紧接着再去申请一块内存,用来存放这个新元素的 Key(
zend_string)。
看出问题了吗?如果偏偏在第 1 步刚做完、第 2 步去申请 Key 的内存时,触发了 memory_limit 内存上限呢?
以前 PHP 的做法是:在内存分配器里直接调用 longjmp() 原地起飞,跳过后面所有的初始化步骤,直接进入“请求结束清理阶段(Request Cleanup)”。
等到清理函数过来打扫卫生、遍历销毁这个哈希表的时候,它看到计数器显示“这里有个元素”,于是顺着指针就去执行 free() 释放内存——可那个槽位刚才根本就没初始化完,里面装的还是之前堆内存里残留的脏数据(攻击者提前通过堆喷射布置好的假指针)!
于是,PHP 的清理函数乖乖地把攻击者伪造的地址当成合法结构体去释放甚至调用析构函数,直接造成堆内存破坏(Heap Corruption),进而拿下 RCE。
而且攻击者是怎么在不超出 post_max_size(比如默认 8MB)的前提下,精准撑爆 64MB 甚至 128MB 的 memory_limit 的?
答案就在解析文件上传的 main/rfc1867.c 里。当攻击者发送一个 multipart/form-data 请求,里面塞满 filename="" 的空分段(不传实际文件,从而绕过文件上传数量限制)和成千上万行自定义 Header 时,旧版 PHP 会老老实实用 estrdup() 把每一行垃圾 Header 都复制进内存链表。实测只需要在网络上发一个 2.9 MiB(约 22 万行短 Header)的请求包,就能在 PHP 进程里膨胀几十倍,精准卡在解析中途引爆 OOM!

2. CVE-2026-109619:前人留下的悬空指针,后人直接踩雷(RCE via Stale Global State)
如果说上面那个洞是“自己拆房砸死自己”,那第二个洞就是典型的跨请求释放后重用(Cross-Request Use-After-Free, UAF)。
PHP 引擎里有很多生命周期跟 Worker 进程一样长的“全局变量”(比如执行器全局变量 EG、Session 全局变量 PS)。有时候,这些全局变量会临时存放一个指向“当前请求内存”的指针。
官方确认了两条最典型的翻车路径:
- 路径一(PHP 8.5 的致命错误回溯
fatal_error_backtraces): 当发生错误时,zend_error_zstr_at()会把当前请求的错误调用栈存进全局变量EG(last_fatal_error_backtrace)里。如果紧接着因为 OOM 强行中断请求,zend_deactivate()在把整个请求内存池释放掉之前,忘了把EG(last_fatal_error_backtrace)清空! 这时候,内存已经还给分配器了,但 Worker 进程身上的EG(last_fatal_error_backtrace)依然指着那块地皮(成了悬空指针 Dangling Pointer)。 紧接着,攻击者发送第二个请求给同一个 Worker。只要在请求刚启动(正常的初始化覆盖还没执行)时故意触发一个极早期的警告(比如 INI 配置警告),再次进入zend_error_zstr_at(),代码就会执行ZVAL_UNDEF(&EG(last_fatal_error_backtrace))去销毁旧数据——砰!它直接去解引用了那块已经被攻击者重新占坑的堆内存,完美触发 UAF! - 路径二(
ext/session/session.c的上传进度session.upload_progress): 当你在上传文件并且带上了PHP_SESSION_UPLOAD_PROGRESS参数时,PHP 会在请求启动阶段(业务脚本运行前)自动去读取、反序列化并更新 Session。如果在这个过程中故意触发异常中断,不仅全局 Session 状态没能干净重置,攻击者还能借着跨请求的残留状态泄露 Worker 进程内存(表象就是服务器返回的Set-Cookie: PHPSESSID=...里直接带出了一大串%编码的堆内存地址),绕过 ASLR 后通过劫持zval_ptr_dtor->zend_call_function直接执行系统命令。
四、 扒一扒 php/php-src 的万行提交:PHP 官方到底改了什么?
这次最让我感慨的,是去 github.com/php/php-src 看官方的修复 Commit。
在 PHP 8.5 分支上,这笔提交的 Hash 是 31c0ea7d7ff1d1fccb6492e52dab544011628065(在 8.2 分支上是 3584a98bda0f2c0d367255b046798b0d3a221a19)。
仅仅这一笔 Commit,在 8.5 分支上就改动了 423 个文件,新增 11,556 行代码,删除 1,017 行代码(其中有 260 多个文件是新增的 .phpt 回归测试,占了工作量的 70% 以上)。
它的 Commit Message 写得非常克制,只有短短几行:
Avoid state corruption via bailout
- Replace bailout on OOM with UnwindExit
- Properly reset various globals that may be accessed during startup
Fix GHSA-w8hw-w9fx-j79w (CVE-2026-109618)
Fix GHSA-6wmf-4rv5-p3rp (CVE-2026-109619)把这一万多行 C 代码拆开来看,PHP 核心团队主要动了以下三刀大手术:

第一刀:废除内存分配器里的“原地 longjmp()”,改用 delayed_oom + 双层内存水位线
这是改动最大、也最伤筋动骨的地方(主要集中在 Zend/zend_alloc.c、Zend/zend_hash.c、Zend/zend_execute_API.c 等核心文件)。
以前在 C 函数里调用 emalloc() 一旦超标就直接 longjmp() 飞走,现在 PHP 团队彻底改了规矩:
- 引入“软上限”与延迟报错(
delayed_oom): 你以前熟悉的memory_limit(比如 128M),现在变成了软上限(Soft Limit)。当底层 C 函数(比如刚才说的哈希表插入)分配内存撞上memory_limit时,分配器不再原地跳伞,而是把错误悄悄记在执行器新增的EG(delayed_oom)标志位上,先让当前的 C 函数把手里那几行代码跑完,保证内部数据结构是完整、自洽的(或者对于支持失败返回的分配函数,干净地回滚并返回 NULL)。 - 退回安全边界再抛
UnwindExit: 等底层 C 函数安全退回到 Zend VM(虚拟机)的指令边界(比如准备执行下一条字节码、函数调用或跳转检查中断时),通过新增的zend_handle_delayed_oom()集中处理,抛出内部的UnwindExit异常,像正常异常栈展开一样,一步步安全地析构并退出请求。 - 新增硬上限配置
memory_hard_limit_percent: 既然撞上memory_limit后还允许 C 代码把收尾工作做完,那万一某段 C 代码失控、趁机无限申请内存把服务器物理内存吃光怎么办? 为此,这次更新在main/main.c里塞进了一个全新的php.ini配置项:memory_hard_limit_percent。 默认情况下,如果你的memory_limit <= 1GB,硬上限就是软上限的 200%(比如默认128M的软上限,硬上限就是256M;如果大于 1GB,硬上限则是memory_limit + 1GB)。 一旦进程内存冲破了这道硬上限,PHP 不再尝试做任何清理,也不跑任何shutdown_function,直接调用操作系统底层的_exit(137)当场自尽,由父进程重新拉起新 Worker,绝不给内存腐化留半点机会。
第二刀:给 main/rfc1867.c(Multipart 解析器)瘦身,拔掉内存放大器
为了防止攻击者再用几兆的请求包把内存撑爆,官方对 main/rfc1867.c 做了两处极其干脆的改动:
第一,不再当“垃圾 Header 收集癖”。看下面这段真实的 Diff 核心逻辑,修改后的解析器新增了 php_mime_should_save_hdr(line) 判断,除了 PHP 真正用得上的 Content-Disposition 和 Content-Type 之外,其他所有乱七八糟的自定义 Header 统统直接扔掉:
// main/rfc1867.c —— 只保留真正有用的 Header,干掉内存放大器
+ if (!php_mime_should_save_hdr(line) // 仅保留 Content-Disposition / Content-Type
+ || php_mime_get_hdr_value(*header, line)) {
+ key = NULL; // 丢弃其他所有垃圾 Header 行
+ } else {
+ key = estrdup(line);
+ }第二,把 rfc1867.c 里的内存分配全部改成可失败模式(一旦检测到 OOM 标志,立刻在两个字段解析的间隙 goto oom,把已经建好的半成品干净释放掉再退出),并且对实际读取的字节流严格套用 post_max_size 限制。
有博主拿修补前的 PHP 8.4.26 和修补后的 8.4.27 做了对比实测:同一个包含 22 万行 Header(2.9 MiB)的攻击包,打在 8.4.26 上直接触发 64MB OOM Fatal Error(HTTP 500);而打在 8.4.27 上,哪怕加码到 200 万行 Header(27 MiB),内存也稳如泰山,直接正常返回 HTTP 200。
第三刀:全局状态大扫除 + 给 Session 反序列化套上 in_rfc1867_update 紧箍咒
为了彻底封死 CVE-2026-109619,这笔提交横扫了 Zend/、ext/session、ext/standard、ext/mbstring、ext/pcre、ext/opcache 等 20 多个模块:
- 在
executor_globals_ctor(构造)和zend_deactivate(请求结束清理)里,把包括EG(last_fatal_error_backtrace)在内的所有可能跨越请求残留的全局指针全部强制清零。 - 在
ext/session/session.c处理上传进度的地方,不再不管三七二十一强行激活 Session,而是严格检查php_session_initialize()的结果,一旦失败立刻干净返回。 - 更绝的是,官方在上传进度更新逻辑外层包上了一个新的
in_rfc1867_update状态标志,强制把这一步里的unserialize()(反序列化)降级为“仅允许不完整类(incomplete classes only)”模式。这样一来,在请求启动阶段解析上传进度时,绝对不可能再去实例化业务代码里的类或触发__wakeup()/__destruct()魔术方法了。
五、 补丁打完就结束了吗?几个你需要注意的“后遗症”
一次性在底层改动一万多行代码,哪怕有 AI 辅助验证和两百多个新测试兜底,在复杂的生产环境里还是立刻溅起了水花。如果你刚升完级,或者正准备升级,有几个坑一定要留意:
- 别忘了重启 Worker 进程,小心
undefined symbol: zend_handle_delayed_oom报错: 很多用 Linux 包管理器更新、或者用 cPanel 的朋友发现,升完级网站直接挂了,报错日志里刷屏:Failed loading Zend extension 'opcache.so': undefined symbol: zend_handle_delayed_oom。 这是因为主程序和扩展之间新增了zend_handle_delayed_oom这个内部符号。如果你只替换了二进制文件却没重启php-fpm/apache2,或者第三方预编译的扩展包跟 PHP 核心版本不匹配,就会直接找不到符号。升级后务必重启所有 PHP Worker 进程! - PHP 8.4.27 和 8.5.12 的 SimpleXML 崩溃 Bug(Issue
#24488): 新版本放出后不到半天,php-src的 Issue 区就有人报了#24488:在 8.4.27 和 8.5.12 上,当垃圾回收器(GC)清理SimpleXML对象时,有概率触发zend_mm_heap corrupted崩溃(这是因为这两个分支顺带合入了另一笔常规 Bug 修复,而仅包含安全补丁的 8.2.35 和 8.3.36 则不受影响)。如果你的业务重度依赖 SimpleXML,这两天需要紧盯随后的小版本热修。 - 如果你之前听信偏方改了
memory_limit = -1,赶紧改回来! 前面提到,没打补丁前把memory_limit设为-1可以避免触发longjmp。但在打完补丁的新版本里,memory_limit = -1会把软上限和新增的memory_hard_limit_percent硬上限同时关掉。打完补丁后一定要恢复正常的内存限制(另外千万别手欠把memory_hard_limit_percent设成100,那会让所有普通的内存超限都变成直接强杀进程exit 137,导致 Nginx 报 502)。 - 还在用 PHP 7.4 / 8.0 / 8.1 等 EOL 老版本的怎么办?
官方这次给已停止维护的 7.4、8.0、8.1 破例拉了三个源码分支(比如 7.4 的 commit
712e60f,只改了 17 个文件、240 行代码,属于最小化止血补丁)。但要注意:官方没有为这些老分支修改PHP_VERSION版本号字符串(比如打完补丁跑php -v依然显示8.1.34)。如果你用的是 Ubuntu / Debian / RHEL 的发行版维护包,千万不能只看php -v的大版本号,必须用dpkg -l或rpm -q对照发行版的安全公告后缀来确认补丁是否真的生效。
结语
回看这荒诞又紧张的 48 小时,你会发现一个很有意思的对比:
在漏洞挖掘那头,时代已经跑得飞快了——安全研究员带着 GPT-5.6 Sol 和 Gemini 4 Argon,只花几天时间就能把一段潜伏了 14 年、连顶尖人类专家都觉得“不可能利用”的 C 语言陈年老代码,盘成一套全自动、22 秒拿 Shell 的大杀器。
可在漏洞响应这头,我们依然要面对非常“人类”的草台班子现实:一个只有 13 个人的开源基金会撑起了全球 70% 网站的底座;一个 "trust me, bro" 的帖子就能击穿保密防线;一次手滑的视频上传、一篇为了博眼球的半吊子分析、一家为了抢风头提前 6 小时发镜像的商业公司,就能让全世界成千上万的工程师跟着熬通宵。
无论如何,向那几位在过去两个月里默默改掉一万行 C 代码、写下两百多个测试用例的 PHP 核心维护者致敬。如果你还没重启你服务器上的 php-fpm,现在,立刻去敲下那行命令吧。
附录:PHP2Shell 事件完整时间线速查 & 参考来源

关键时间轴(均以 UTC 时间标注,北京时间请 +8)
- 2024 年:Quarkslab 审计期间首次发现 OOM 内存状态异常,但因缺乏完整利用链及内部未达成修复共识而搁置。
- 2026-08-01 ~ 08-24:Calif(使用
GPT-5.6 Sol)、OpenAI 与 Wiz(使用Gemini 4 Argon)分别独立发现并上报漏洞,PHP 团队启动历时两个月的底层重构,原定 10 月 22 日 14:00 UTC 协同披露。 - 2026-10-17:AWS MadPot 蜜罐捕获到首起疑似针对该漏洞的神秘早期探测流量。
- 2026-10-18 15:26 UTC:Arnaud Le Blanc 等人完成核心修复 Commit(
Avoid state corruption via bailout)。 - 2026-10-20 15:00 UTC:Hacker News 用户
petecooper发帖泄密,误传 CVSS 10.0。 - 2026-10-20 19:30 UTC:watchTowr 公开确认漏洞属实;PHP 官方被迫发布 7 行字预告,将发版时间提前一天至 10 月 21 日 14:00 UTC。
- 2026-10-20 20:00 UTC:原发现方 Calif 团队失误公开攻击演示视频,虽秒删但被
@k_firsov截留转发,php2shell代号及关键符号全网扩散。 - 2026-10-20 20:33 UTC:欧盟 CERT-EU 发布
SA2026-018,建议无法立即打补丁的公网 PHP 服务断网下线。 - 2026-10-21 02:00 UTC:野生研究员抢发残缺 PoC,引发全网“只需关闭
session.upload_progress.enabled”的片面缓解误导。 - 2026-10-21 03:02 UTC:Cloudflare 为全球所有免费及付费用户默认部署 WAF 全事务检测拦截规则。
- 2026-10-21 07:41 UTC:容器厂商 Chainguard 提前 6 小时打破保密禁令,公开发布含补丁的镜像。
- 2026-10-21 08:36 UTC:Chainguard 泄露补丁 55 分钟后,ELLIO 等机构捕获到全网第一波真实在野攻击流量;欧美多国政府网站、维基百科支撑服务、日本各大网站及游戏纷纷停机避险。
- 2026-10-21 13:34 UTC:PHP 官方正式发布 GitHub 安全公告及 PHP 8.5.12 / 8.4.27 / 8.3.36 / 8.2.35 修复版本,PHP 基金会随后发文复盘并点名批评 Chainguard。
参考链接与一手资料(References)
- PHP 官方安全公告与缓解指南:
https://www.php.net/archive/2026.php#2026-10-21-5 - PHP 基金会官方复盘长文:
https://thephp.foundation/blog/2026/10/21/todays-php-security-release-and-how-were-strengthening-php-security/ - GitHub 官方安全公告(GHSA):
- CVE-2026-109618 (OOM corruption):
https://github.com/php/php-src/security/advisories/GHSA-w8hw-w9fx-j79w - CVE-2026-109619 (Stale global state):
https://github.com/php/php-src/security/advisories/GHSA-6wmf-4rv5-p3rp
- CVE-2026-109618 (OOM corruption):
php/php-src核心修复 Commit Diff:- PHP 8.5 分支 (
31c0ea7):https://github.com/php/php-src/commit/31c0ea7d7ff1d1fccb6492e52dab544011628065 - PHP 8.2 分支 (
3584a98):https://github.com/php/php-src/commit/3584a98bda0f2c0d367255b046798b0d3a221a19
- PHP 8.5 分支 (
- 漏洞发现方技术报告:
- 联合披露主页:
https://www.php2shell.com/ - Calif.io 技术 Write-up:
https://calif.io/php2shell/ - Wiz 官方博客:
https://www.wiz.io/blog/php2shell-php-rce - OpenAI 官方博客(Patch the Planet):
https://openai.com/index/coordinated-disclosure-of-critical-vulnerabilities/
- 联合披露主页:
- PHP 安全团队成员 Alexandre Daubois 辟谣文章:
https://daubois.dev/blog/cve-2026-109618-php2shell-rumors/ - Cloudflare WAF 防护分析:
https://blog.cloudflare.com/php-vulnerabilities-waf-protection