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的输出。
参考链接
- PHP 官方 8.3.36 发布说明
- PHP.Watch — PHP 8.3.36 Release Information
- CVE-2026-109618 / GHSA-w8hw-w9fx-j79w
- CVE-2026-109619 / GHSA-6wmf-4rv5-p3rp
- The PHP Foundation — Today's PHP Security Release
- PHP2Shell: How to Check That Every PHP You Run Is Actually Patched
- CNVD 关于 PHP 存在远程代码执行漏洞的安全公告
- 山石网科 — PHP2Shell 安全通告
最后更新:2026-10-22
评论 2
看来还还得是静态化,不怕被攻破。
适当做一些防御,对于博客还是没问题的。