PHP2Shell 爆了,我回头检查了自己的博客

2026年10月21日,PHP官方一次性发了8.5.12/8.4.27/8.3.36/8.2.35,修了内核里藏了很久的两个严重漏洞,社区代号PHP2Shell。CVSSv4.0双双9.2,免登录RCE。本文记录我对自家博客(infowe.sitePHP版)的一次排查与加固过程。


当前博客使用的版本为 Python & Flask


一、这几天全网在吵什么

一句话:不是你代码烂,是 PHP 地基裂了。

2026 年 10 月 21 日 14:00 UTC,PHP 官方发布紧急安全更新,修复两个可导致未认证远程代码执行的漏洞 [citation:2][citation:10]:

项 内容
社区代号 PHP2Shell
CVE CVE-2026-109618 / CVE-2026-109619
GitHub 安全公告 GHSA-w8hw-w9fx-j79w / GHSA-6wmf-4rv5-p3rp
风险等级 严重(CVSS v4.0 9.2)
认证要求 无需登录,未认证即可触发
利用条件 特制 HTTP 请求,PoC 已公开,暂未发现大规模在野利用 [citation:12]

受影响版本

分支 修复版本 备注
8.5.x 8.5.12 —
8.4.x 8.4.27 —
8.3.x 8.3.36 ✅ 本站当前版本
8.2.x 8.2.35 2026-12-31 EOL
8.1 及更早 无官方修复 官方不再维护

我的博客底层就是 PHP 8.3.36,正好落在官方修复版本上。但光"版本号对"不等于没事,后面会讲为什么。


二、漏洞原理(通俗版)

两个 CVE 共用一个根因:PHP 中途放弃一个操作时,留下了不该出现的内存状态。官方 changelog 只用了一句话概括:Avoid state corruption via bailout [citation:7][citation:14]。

Bug 1:内存不足的"降落伞"开得太早

CVE-2026-109618 — OOM 导致的内存破坏

请求跑到 memory_limit 时,PHP 不会优雅地一层层返回,而是用 longjmp() 直接跳出——PHP 内部管这叫 bailout。问题是,如果跳出恰好发生在请求数据还没初始化完的时刻,收尾清理就会把"半成品"当成合法数据去处理,进而产生内存破坏 [citation:7][citation:10]。

更阴的地方在于:你可以在没超过 PHP 认为的限额时就触达 OOM。比如 multipart 上传没有可用的 Content-Length,会绕过 post_max_size;multipart 各部分的 header 在 PHP 内部占用的内存远高于线上传输量;空的文件字段还不计入 max_file_uploads [citation:17]。

最坏形态是 Apache + mod_php:默认配置下,一条未认证请求就能在应用代码跑起来之前触发漏洞并完成 RCE,PHP 官方已给出实际演示 [citation:4][citation:8]。

Bug 2:上一个请求的"鬼魂"缠上下一个请求

CVE-2026-109619 — 残留全局状态导致的 UAF

PHP worker 是常驻的,一个接一个处理请求。错误、会话、扩展数据等全局状态,可能还指向上一个请求的内存。如果一次致命错误发生在错误时机,内存被释放了、指针却还在原位;下一个请求在被重置之前碰到了这块状态,就成了经典的 use-after-free [citation:7][citation:10]。

已确认的触发路径:session.upload_progress 特性,以及 PHP 8.5 新增的 fatal_error_backtraces [citation:17]。

一句话总结两条触发链:

OOM / 致命错误
    ↓ longjmp 跳出
请求数据初始化到一半 ──→ 收尾清理把垃圾当合法数据 ──→ RCE
worker 全局指针还指着已释放内存 ──→ 下一请求来读取 ──→ UAF / RCE

三、为什么"我代码没 bug"也没用

这两个洞都在 Zend 引擎的请求生命周期和内存管理里,几乎所有请求都会经过的位置 [citation:10]。不管你首页写得多么干净、输入校验做得多严,攻击者发的那条恶意请求走的是内核路径,根本到不了你的业务代码。

  • mod_php 下甚至应用代码没跑就崩了;
  • PHP-FPM 下,OOM 路径"源码分析层面易感",session.upload_progress 路径已验证为 RCE [citation:4][citation:8]。

所以完整修复只有一条路:升级 PHP。官方原话是 "There are no complete mitigations, updating is the only safe choice."(没有完整的缓解方案,更新是唯一安全的选择)[citation:2][citation:17]。


四、我的博客踩中了哪些风险点

infowe.site 跑的是自己写的 PHP 版博客(单管理员、MySQL、自带安装向导)。对照漏洞特征,有几处正好踩在风险面上。

1. 运行目录就是项目根,敏感文件暴露面大

博客的部署方式是:网站运行目录直接指向项目根,于是 config/config.php(含数据库密码)、core/、storage/、data/、backups/、bin/、plugins/、docs/、fonts/ 全都在 Web 可见范围内。靠根目录 .htaccess(Apache)和 Nginx 的 deny all 规则兜底。

这意味着:一旦 PHP2Shell 被打穿,攻击者拿到的就是 Web 进程身份,这些目录可以直接被读——锁规则只要写错一条,源码和数据库密码就可能泄露。

# 我的 Nginx 配置里必须保留的封锁(存在即合理)
location ^~ /config/   { deny all; }
location ^~ /core/     { deny all; }
location ^~ /storage/  { deny all; }
location ^~ /data/     { deny all; }
location ^~ /backups/  { deny all; }
location ^~ /bin/      { deny all; }
location ^~ /plugins/  { deny all; }
location ^~ /docs/     { deny all; }
location ^~ /fonts/    { deny all; }

# 主题层:模板源码与 info.json 也要锁
location ~* ^/themes/.+\.(html|json)$ { deny all; }

⚠️ 坑提醒:千万别顺手加一条 location ~* \.sql$ { deny all; } 之类的"后缀黑名单"。后台备份下载走的是 /admin/export/download/*.sql,会被一起拦成 404,现象就是点"下载"返回 404。这跟宝塔的"防跨站攻击"(open_basedir)没关系——那个只限制 PHP 读文件路径范围,越界报的是 open_basedir restriction 警告/错误,不是 404。

2. 安装向导是潜在的"重装入口"

站点通过 config/installed.lock 判断是否已安装;删除 installed.lock + config.php 并清空数据库,就能重新走向导。

如果攻击者已经通过 RCE 拿到了写权限,删掉这两个文件后,下一步就是用向导建一个新管理员。

加固动作:

  • 安装完成后删除 install.php(根 index.php 内置了 installed.lock 判断,删了也安全);
  • config/ 目录除 deny all 外,文件权限收回 chmod 440;
  • 数据库账号别用 root,按库授权 GRANT ALL ON infowe_blog.*。

3. PHP-FPM 不是免死金牌,但确实比 mod_php 稳

本站跑的是 Nginx + PHP-FPM。官方口径:mod_php 最危险,FPM 也要升。所以"我是 FPM"绝不是不升的理由。

4. 单管理员 + 内核层兜底,依赖补丁

博客的单管理员约束是程序层保证的(后台不提供创建第二个账号的入口);MySQL 触发器那条由 SUPER 权限兜底的路径,在只授单库权限的账号下是被向导自动跳过的。这些"非默认配置下的功能缺口",本质都是靠干净的运行环境和及时补丁来收敛风险。


五、我的处理动作

1. 确认版本

php -v
# PHP 8.3.36 (cli) ...

8.3.36 在官方修复列表里,符合。

但这里有个大坑要注意:

  • CLI 和承载请求的 FPM/mod_php 可能是两个二进制、两份 php.ini,php -v 只能证明 CLI 那份;
  • 发行版号 ≠ 上游号:Ubuntu 的修复包可能还是 php8.3 8.3.6-0ubuntu...,页面显示 8.3.6,按 8.3.36 去比对会误判成未修复,要查包版本和 changelog 里的 CVE;
  • 面板与容器同理:宝塔显示的版本未必是真正在跑的版本,改完必须重启 PHP 服务,只重启站点没用;Docker 要 docker build --pull 重建,缓存的基础镜像不带补丁 [citation:5][citation:11]。

稳妥做法:临时放一个页面输出 PHP_VERSION、PHP_SAPI、php_ini_loaded_file(),读完马上删。

2. 升级到 8.3.36

走包管理器升级(或发行版 backport 包),装完确认是修复版本。

3. 重启所有常驻 worker(最容易被漏)

只换包不重启,老 worker 还带着洞。

# PHP-FPM
systemctl restart php8.3-fpm

# Apache + mod_php
systemctl restart apache2

# 宝塔:软件商店 → 对应 PHP → 重启

容器环境要重建并替换运行中的容器,然后检查容器里实际在跑的是什么版本,而不是看镜像仓库元数据(有统计显示发布后 5.5 小时仍有部分流行 PHP 镜像在出带洞版本)[citation:11]。

4. 核对 Nginx 封锁规则

逐条比对上面的目录与模板封锁,确认没有被误删。同时检查 config、core、storage、backups、themes/ 的 deny all 规则完整。

5. 回收文件权限

chmod 440 config/config.php
chmod 440 config/installed.lock
chown -R www-data:www-data storage/ backups/ data/

6. 数据库账号降权

确认 Web 用的数据库账号是单库授权,不含 SUPER/GRANT 全局权限。


六、临时缓解(升不了版本时用)

官方明确说这些措施只阻断已确认的路径,不是完整防护,不能作为不升级的借口 [citation:13]。

CVE-2026-109618(OOM 路径)

; 要在应用层无法覆盖的位置设置(FPM 池用 php_admin_value,Apache 用 php_admin_value)
memory_limit = -1
post_max_size = 8M        ; 无上传需求的业务;有上传给 32M
upload_max_filesize = 8M  ; 保持低于 post_max_size
  • 在反向代理 / Web 服务器层限制请求体大小、multipart 段数和 header 数量;
  • 用容器或 cgroup 限制内存,让 OOM 只杀掉对应 worker;
  • 不要用 RLIMIT_AS / RLIMIT_DATA,那会触发有漏洞的分配失败 [citation:4]。

CVE-2026-109619(残留状态路径)

; 必须写在 php.ini / FPM 池 / 服务器配置里,不能通过 ini_set() 或 .user.ini 覆盖
session.upload_progress.enabled = 0

; PHP 8.5 额外关闭
fatal_error_backtraces = 0

改完必须重启全部 worker 才生效。

Nginx 侧也可直接对非必要场景的整体拦截 multipart,但会影响上传和表单,需按业务权衡。


七、排查是否被攻破

PoC 从 10 月 17 日前后就开始流出,可以从那个时间点起算排查 [citation:11]。

# 找近期新增的可疑 php 文件
find /www/wwwroot -name "*.php" -newermt "2026-10-17" -ls

# PHP 错误日志里的 OOM / memory exhausted
grep -R "Allowed memory size" /www/wwwlogs/ /var/log/

# worker 异常崩溃(segfault / child exited)
journalctl -u php8.3-fpm | grep -iE "segfault|child exited|signal"

# 异常外连与可疑进程
ss -antp | grep php
ps auxf | grep -E "php|www-data" | grep -vE "fpm: master|fpm: pool"

重点痕迹:

  • backups/ 目录出现不认识的 .sql 或 .php;
  • storage/ / uploads/ 里多了陌生文件;
  • config.php 时间戳被改动;
  • 数据库 users 表多出管理员、或密码字段被改;
  • Web 用户下出现不明 crontab、SSH key、反弹 shell。

升级补丁不会修复已经发生的入侵。发现入侵痕迹应按事件响应流程处置:隔离 → 取证 → 重建 → 恢复。


八、小结

这次事件最值得记住的一句话:开源建站,"能跑"和"安全"是两件事。

  • 漏洞不在你的代码里,在地基里;
  • 版本号对 ≠ 已修复,CLI 版本 ≠ FPM 版本,镜像仓库版本 ≠ 运行容器版本;
  • 装完包必须重启 worker;
  • 目录封锁、文件权限、数据库降权、收掉安装入口,是平时就该做好的"第二道防线";
  • PHP 8.2 分支 2026-12-31 EOL,8.2 的用户该规划升级到受支持分支了。

本站(infowe.site)已完成升级至 PHP 8.3.36 并重启 worker,封锁规则与权限已核对。这是一次运维层面的自查记录,不构成专业安全建议——如果你也跑着自建博客,建议按上面清单逐条核一遍,而不是只看 php -v 的输出。


参考链接

最后更新:2026-10-22

评论 2

Mr.He
Mr.He 安徽 安庆

看来还还得是静态化,不怕被攻破。

豪子
豪子 博主 重庆

适当做一些防御,对于博客还是没问题的。

写评论
0 / 2000