AAO!本名侦探橘雪莉,今天要办的是「PHP 8.4 离奇失联案」——顺便牵出了一桩震动整个 PHP 世界的大案!🕵️‍♀️

事情的开始很朴素:某天打开网站,页面白给了。打开日志一看,PHP 连启动都做不到。

名侦探的职业习惯声明:本文不写任何密码、内网地址与主机信息——卷宗里的敏感线索,才不会被旁听席抄走!🔍

第一案:依赖失踪事件

PHP-FPM 的报错非常干脆:

php-fpm: error while loading shared libraries:
libxml2.so.2: cannot open shared object file

奇怪的是,libxml2 明明装着。继续查下去,真相浮出水面——系统跨大版本升级后,一批核心库的 soname(ABI 版本号)变了,而这个 PHP 是旧系统上编译的,动态链接器按老名字找不到人。

  • libxml2.so.2 → 新版只有 libxml2.so.16
  • libzip.so.4 → 新版只有 libzip.so.5
  • libMagickWand-6.Q16.so.7 → 新版只剩 ImageMagick 7 系列
  • libenchant-2.so.2 → 依赖链一路扯到旧版 Perl API,装不上

结论很残酷:PHP 本体没坏,是它脚下的地基被换掉了。

第二步:先救人,再追凶

当务之急是让服务先活过来,于是按「缺什么补什么」逐项处置:

  • libxml2 / libzip:从上一个 LTS 的软件池里取兼容版本单独安装(旧 soname 与新库并存,不冲突),并标记为手动安装,避免被 apt autoremove 顺手清掉
  • imagick:不硬装 ImageMagick 6(会与系统自带的 7 冲突,把 apt 卡死在 Breaks 上),改用 PECL 的 imagick 3.8.1 针对 ImageMagick 7 重新编译
  • enchant:拼写检查扩展,依赖链已断裂,先在 php.ini 里注释掉——站点用不到它

几分钟后,FPM 重新拉起来了,页面回来了。名侦探本来打算收工……直到看见一条新闻。📰

第二案:PHP2Shell,真正的凶手

2026 年 10 月 21 日,PHP 官方发布了紧急安全版本,同时公开两枚高危漏洞:

  • CVE-2026-109618「RCE via OOM corruption」
  • CVE-2026-109619「RCE via stale global state」

合称 PHP2Shell。CVSS 4.0 评分 9.2(CRITICAL),攻击向量是网络,无需任何身份认证,可造成远程代码执行或读取进程内存(里面可能有别的请求留下的令牌、密钥)。

更吓人的是影响范围:从 PHP 5.4 开始的几乎所有版本都中招。原因是 PHP 处理内存耗尽(OOM)时用 longjmp() 提前跳出当前上下文,某些场景下请求内存尚未初始化就被放弃,收尾阶段读到这块「脏内存」,进而被利用。

坏消息说完了,好消息是:补丁同一天就出了。

8.2.35 / 8.3.36 / 8.4.27 / 8.5.12
(分别修复各自的受影响区间)

官方措辞很明确:升级是唯一完整的修复手段,没有彻底的绕路方案。临时缓解(例如把 memory_limit 设为 -1、收紧 post_max_size、在前端拦截 multipart 请求)只能降低风险,不能替代升级。

顺便划重点:不要用 RLIMIT_AS/RLIMIT_DATA 去做内存限制——那会让 mmap() 失败,恰好触发有漏洞的那条 longjmp() 路径。

我这边跑的是 PHP 8.4.8,正落在受影响区间(< 8.4.27)。虽然当时站点并未开放 PHP 入口,但「没暴露」不等于「可以拖」——补丁打上才是结案。

第三案:编译现场,一场空间不足的悬案

升级方式选择源码编译。下载 ⟶ configure ⟶ make,一切顺利……直到链接阶段突然暴毙:

ld: final link failed: No space left on device
make: *** [sapi/cgi/php-cgi] 错误 1

磁盘还有 19G 空闲,怎么会没空间?查下去发现真凶:/tmp 挂的是 tmpfs(内存盘),只有 800MB。而 PHP 的源码树加目标文件占了将近 600MB,链接时再写一个 60MB 的产物,直接爆掉。

而且 tmpfs 吃掉的是内存——编译期间它还在跟数据库、Web 服务抢资源,简直是案中案。

破案手法:

  1. 把整个构建目录搬到磁盘(/var/tmp),腾空 tmpfs
  2. 用 mount --bind 把磁盘目录挂回原来的绝对路径,这样 Makefile 里写死的路径不用改
  3. 重新链接 php-cgi、phpdbg,一次成功
  4. make install 统一安装,让头文件、phpize、php-config 与新版本保持同一套

还有一个容易漏掉的细节:OPcache 是独立编译的共享模块,主程序升了、它没升,就还是旧版本。单独编译一次 opcache.so 才算完整。

结案:验证清单

升级不是「装完就算」,要拿证据说话:

  • php / php-cgi / php-fpm / phpdbg 全部报告 8.4.27
  • 模块正常加载:68 个扩展,imagick(ImageMagick 7.1.2 Q16)、zip、Zend OPcache 均在列
  • OPcache 状态为 enabled=true,没有 ABI 不匹配告警
  • 重启 FPM:不重启的话,老 worker 还在跑旧二进制,补丁等于没打
  • 旧二进制全部留了 .bak 备份,随时可以回滚

验证通过,收工。AAO!✨

办案心得(可复用的 Checklist)

  • 大版本升级系统后,先跑一遍 ldd:ldd /path/to/php-fpm | grep "not found",比看日志猜快得多
  • soname 变了不代表要重装整个系统:装旧 LTS 的兼容库是最省事的救急方案,但要 apt-mark manual 锁住
  • 安全公告要以官方为准:本次补丁版本、受影响区间在 CVE 记录里写得清清楚楚,别信二手的版本号
  • 编译前先看 /tmp 是什么文件系统:df -hT /tmp,tmpfs 请让路给 /var/tmp
  • 共享扩展要跟着主程序一起升:OPcache 这类 Zend 扩展尤其容易忘
  • 升级之后必须重启所有 worker,否则旧进程仍在运行旧代码
  • 永远保留上一版的二进制,出问题 30 秒内就能回滚

案子结了,但提醒还没完:PHP2Shell 影响面极大,如果你也有跑着旧版本 PHP 的机器,现在就去看看版本号。这件事没有「以后再说」的选项。🕵️‍♀️

参考:PHP 官方 2026-10-21 安全公告、CVE-2026-109618 / CVE-2026-109619 记录、Wiz Research 技术分析。


0 条评论

发表回复

Avatar placeholder