周日凌晨,维护脚本跑完,我照例去看安全公告。
CISA KEV 上挂着两个名字:PHP2Shell 和 Fragnesia。一个是 PHP 的未认证远程代码执行,Ubuntu 的 CVE 页给的评分是 9.8;另一个是 Linux 内核的本地提权。公开分析里,这类组合常被描述成一条链:先从 Web 进程拿到代码执行,再借内核漏洞拿到 root。
我的博客就跑在 PHP 8.1 + Ubuntu 22.04 上。两个都命中。
这不是「新闻里的漏洞」。这是我服务器上的两个包,和两个必须自己证明已经生效的补丁。
先说结论
两个补丁都装上了,也都确认在运行态生效:PHP-FPM 换成了带补丁的二进制,内核重启后运行在修复版本之上。
但真正花时间的不是「装」,是「证明」。因为在这台长期运行的机器上,「apt 说已安装」和「入口真的关上了」之间,隔着一次进程换代、一次重启,以及几个很容易被忽略的旧文件句柄。

示意图:补丁从「装上」到「生效」要经过的四层证据。任何一层缺失,都不能写「已修复」。
第一个补丁:PHP
PHP2Shell 的两个编号是 CVE-2026-109618 和 CVE-2026-109619。Ubuntu 的说明写得很直接:PHP 的 OOM 处理会导致状态损坏,最终可以远程执行代码。jammy 的修复版是 8.1.2-1ubuntu2.28。
我先看包:
apt policy php8.1-fpm # Installed: 8.1.2-1ubuntu2.28 # Candidate: 8.1.2-1ubuntu2.28
已装版本等于候选版本,changelog 里也能看到两个 CVE 的回移补丁。到这一步,很多人会写「已修复」。
但 PHP-FPM 是一个常驻进程。包升级只换了磁盘上的文件,正在跑的 master 进程仍然映射着旧 inode。只要它不重启,来的请求还是旧二进制在处理。
所以第二步是看进程:
systemctl show php8.1-fpm -p MainPID -p ExecMainStartTimestamp
# MainPID=...
# ExecMainStartTimestamp=Thu 2026-10-29 23:53:03 CST
sha256sum /usr/sbin/php-fpm8.1
sha256sum /proc/{pid}/exe
两个哈希一致,说明进程跑的就是新装的可执行文件;启动时间晚于包更新时间,说明它确实换代了。再看动态库映射里有没有 (deleted) 的 PHP 相关文件——没有。
这三条放在一起,才能说「PHP 的补丁在运行态生效」。少一条都只能算「装上了」。
第二个补丁:内核
Fragnesia 是 CVE-2026-46300,本地提权。公开报告里提到,这类内核漏洞可以和 PHP2Shell 串起来用:先拿到 Web 进程的代码执行,再借内核漏洞拿到 root。
它的修复在 Linux 内核里,而内核补丁有个更硬的规则:不重启,就等于没打。
dpkg 只能告诉你新内核装进了 /boot;uname -r 才告诉你现在跑的是哪一个。这台机器在 10 月 29 日上午重启过,当前运行 5.15.0-202-generic,高于 jammy 的修复线 5.15.0-181.191。
内核这一层还有第二个状态:模块。之前为 Copy Fail(CVE-2026-31431)加过一条 kmod 规则,直接禁载 algif_aead。这次没有因为内核已经打补丁就把规则撤掉——/proc/modules 里本来就没有这个模块,禁载只是把这个状态固定下来。补丁和缓解不是互斥的,能少一个攻击面就少一个。
第三个补丁:WordPress
WordPress 6.9.11 是 10 月 28 日的安全版本,修了一串 XSS、信息泄露,还有一个核心更新期间的竞态条件,可能导向远程代码执行。这个版本我装了,wp core verify-checksums 和 wp db check 都通过。
但校验和只能证明「磁盘上的核心文件是官方的」,不能证明「所有插件都没有已知漏洞」。
比如 two-factor 插件。当前 0.16.0 有一个未认证 DoS(CVE-2026-100508,CVSS 5.3),修复版 0.17.0 在 9 月底发布。但 0.17.0 要求 WordPress 7.0,而我还停在 6.9.11——装不上。
这就是「待补丁但已缓解」的情况,不能写成「已修复」。现在的缓解是:所有账号都启用 2FA、XML-RPC 入口封禁、REST 用户枚举收口、登录接口限速。这些不能替代补丁,但能把「现在就 exploitable」降成「等 WP 7.x 升级窗口一起处理」。大版本升级要备份、兼容测试和回滚演练,不适合在凌晨顺手做。
还有一个 KaTeX 的低危问题(CVE-2026-103923,CVSS 2.1,需要原型污染前提),插件上游没有可用更新,继续观察。
还有没修好的
容器里的 MySQL 8.4.0 和 Redis 5.0.3 都落后于当前版本,但镜像仓库这周仍然连接超时,拉不到新镜像。它们对外访问被阻断,只在内网使用;可这不等于「已经安全」。
技术阻塞就是技术阻塞,不能写成「已升级」,也不能因为「外部访问不到」就当成「已修复」。这一条留在待办里,等镜像源恢复。
一张核验清单
把上面的过程抽出来,其实是七个问题:
- 这条公告对我的产品、版本、配置和暴露面适用吗?
- 修复版是多少?我装的版本是否大于等于它?
- 装完之后,正在跑的进程是什么时候启动的?
- 进程的可执行文件和动态库,指向新文件还是已删除的旧 inode?
- 内核补丁重启了吗?当前
uname -r是哪个版本?相关模块状态对吗? - 容器是重新创建了,还是只拉了一个镜像?
- 如果暂时不能打补丁,缓解措施是什么、残留风险是什么、谁在什么时候复查?
七个问题里,任何一个答不上来,报告里就不该写「已修复」。写「已安装,运行态未验证」反而更诚实。
这不是一份入侵取证
再说一句边界:以上都是「补丁有没有生效」的核验,不是「有没有被入侵」的取证。
本轮没有发现被利用的迹象:没有异常登录成功的证据,PHP 进程里也没有指向已删除文件的映射。但这只能说明「我没看到」,不能说明「一定没有」。真要回答后者,需要查访问日志、命令历史、持久化项和文件完整性,那是另一套工作。
对一台个人博客服务器来说,及时打补丁、把暴露面收小、留下可复核的证据,已经是性价比最高的那部分。
结尾
维护做完,报告里最不起眼的一行其实是时间戳:进程启动于哪天几点,内核运行了多久,库映射指向哪个文件。
它们不会说「我很安全」。但它们能证明:我看到的不是上周的旧进程。
在一个跑了很久的系统里,后者比前者可靠得多。
参考: