全网紧急排查!PHP 连发两条 9.2 分高危漏洞:光 `php -v` 合格,你的线上可能还在裸奔!
10 月 21 日,PHP 官方发布了四个紧急安全版本:PHP 8.5.12、8.4.27、8.3.36 和 8.2.35。
它们修复了两条可能造成未认证远程代码执行的核心漏洞:CVE-2026-109618 和 CVE-2026-109619,CVSS v4.0 评分均为 9.2。
根据 PHP 官方安全公告,所有处理 Web 请求的 PHP 安装都需要排查,包括 PHP-FPM、Apache mod_php、php-cgi、FrankenPHP 和 LiteSpeed。官方认为 mod_php 风险尤其高,因为已经证明未认证请求能够在应用代码执行前触发攻击。
这意味着,无论你运行的是 WordPress、Laravel、Magento,还是自研 PHP 系统,问题都不只是某个框架、插件或业务接口。升级 Composer 依赖、关闭一个路由、修改业务代码,都不能代替修补 PHP 本身。
早期消息强调“补丁发布后的黄金 15 分钟”,但真正落地时,我更关心三件事:你是否找全了线上所有 PHP,是否确认 Web 流量实际使用的是新二进制,以及旧 Worker 是否已经全部退出。
这次事件最值得记住的一句话是:PHP 软件包升级完成,不等于漏洞修复完成;只有处理请求的旧进程全部退出,修复才真正生效。
两条漏洞到底发生了什么
两条漏洞都和内存生命周期有关,但触发机制并不相同。完整技术细节应以 PHP 官方发布的两份 GitHub Security Advisory 为准。
CVE-2026-109618:OOM 处理中的内存破坏
CVE-2026-109618 官方技术公告显示,PHP 在处理请求期间发生内存限制错误时,内部请求数据可能只完成了部分初始化。程序随后进入请求清理流程,却把这些无效数据当成正常对象处理,最终造成内存破坏并可能演变成远程代码执行。
日志中的下面一类错误值得重点关注:
PHP Fatal error: Allowed memory size of ... bytes exhausted但它不能单独证明服务器遭到攻击。大数组、图片处理、批量导入、低效 SQL 或递归调用,同样可能导致 PHP 内存耗尽。
正确的判断方法是建立关联:
- 同一时间是否出现来源异常的高频请求;
- 请求体、表单字段或 multipart 分段是否明显异常;
- PHP-FPM Worker 是否崩溃或被重新拉起;
- 系统是否出现段错误、Core Dump 或 OOM Kill;
- Web 目录、临时目录和计划任务是否出现未知文件;
- PHP 进程是否建立了不符合业务特征的外连。
单条内存错误只能作为线索,不能直接写进事故报告当作入侵证据。
CVE-2026-109619:释放后的全局状态残留
CVE-2026-109619 官方技术公告显示,某次请求使用的内部状态可能在底层内存释放后继续残留。同一个长期运行的 Worker 处理后续请求时,可能在正常初始化之前重新使用这段过期状态。
官方确认的相关路径包括会话上传进度;PHP 8.5 还涉及致命错误回溯路径。
这也解释了为什么修改配置后必须重启全部 PHP Worker:旧进程不仅可能继续加载旧代码,还可能保留旧配置及跨请求残留的内部状态。
先别急着重启,10 分钟内把真实资产找全
应急处置最常见的错误,是登录一台服务器执行 php -v,看到版本合格就宣布修复完成。
CLI、PHP-FPM 和 Apache mod_php 可能来自不同软件包,甚至可能属于不同版本。容器里的 PHP 与宿主机也没有必然关系。第一步必须确认谁在实际处理线上请求。
下面都是只读检查,可以先执行并保存结果。
检查命令行版本、SAPI 和配置来源:
date -Is
hostname -f
php -r 'echo "version=", PHP_VERSION, PHP_EOL,
"sapi=", PHP_SAPI, PHP_EOL,
"ini=", php_ini_loaded_file(), PHP_EOL;'
php --ini如果输出中的 sapi 是 cli,只能说明命令行环境的版本,不能证明 Web 请求也由这个版本处理。
继续检查实际运行的服务:
ps -eo pid,lstart,user,cmd \
| grep -E '[p]hp-fpm|[p]hp-cgi|[a]pache2|[h]ttpd|[f]rankenphp|[l]itespeed'重点记录 PID、启动时间、运行用户和二进制路径。如果一台主机存在多个 FPM 主进程,需要逐个确认监听端口、Unix Socket、Pool 配置和对应站点。
PHP-FPM 环境还可以检查:
command -v php-fpm || true
php-fpm -v 2>/dev/null || true
php-fpm -tt 2>&1 | sed -n '1,160p'部分发行版会把二进制命名为 php-fpm8.4、php8.4-fpm,或者将不同版本放入独立目录。不能因为 php-fpm 命令不存在,就认定服务器没有运行 FPM。
Debian、Ubuntu 系统查看已安装的软件包:
dpkg-query -W \
-f='${Package}\t${Version}\t${Status}\n' \
'php*' 2>/dev/null \
| grep 'install ok installed'RHEL、Rocky Linux、AlmaLinux、CentOS Stream 可执行:
rpm -qa --qf '%{NAME}\t%{VERSION}-%{RELEASE}\t%{ARCH}\n' \
| grep -E '^php'不要只按上游版本字符串判断发行版软件包是否安全。Linux 发行版或商业 LTS 厂商可能把安全补丁反向移植到旧版本,同时保留原来的主版本号。最终要结合发行版安全公告、软件包 Release 字段和变更记录判断。
PHP 官方的版本生命周期可以在 Supported Versions 页面查询;上游版本变更记录可以查看 PHP 8 ChangeLog。
容器和 Kubernetes 别只检查宿主机
容器环境先输出运行清单:
docker ps --format \
'table {{.ID}}\t{{.Image}}\t{{.Names}}\t{{.Status}}'
docker exec <container> php -r \
'echo PHP_VERSION, " ", PHP_SAPI, PHP_EOL;'还要确认镜像摘要,避免同一个浮动标签对应不同构建:
docker inspect <container> \
--format '{{.Config.Image}} {{.Image}}'Kubernetes 环境先确认上下文,避免查错集群:
kubectl config current-context
kubectl get pods -A \
-o custom-columns='NS:.metadata.namespace,POD:.metadata.name,IMAGES:.spec.containers[*].image'再对确认过的 PHP 容器执行:
kubectl exec -n <namespace> <pod> -c <container> -- php -v不要对整个大型集群无差别并发执行 kubectl exec。更稳妥的做法是先导出 Deployment、StatefulSet、DaemonSet、Job 和 CronJob 的镜像清单,按镜像摘要去重,然后检查代表性容器。
资产可按暴露程度分级:
- P0:公网直接可达,且处理上传、导入、富文本、支付回调等复杂请求;
- P1:公网可达,但前面有 CDN、WAF 或 API 网关;
- P2:仅内网访问,但会接收不可信来源的数据;
- P3:离线任务、开发测试和已停止实例。
优先级只决定处置顺序,不代表低级别资产可以不修。
暂时不能升级时,官方建议怎样止损
PHP 官方明确表示:不存在完整的配置缓解方案,升级是唯一安全选择。
下面的配置只能降低部分已确认攻击路径的风险,不能将主机重新标记为安全。
缓解 CVE-2026-109618
官方建议将 memory_limit 设置为 -1,并放在应用不能自行覆盖的位置。PHP-FPM 可以在对应 Pool 中配置:
php_admin_value[memory_limit] = -1这可以去掉攻击者相对容易控制的 PHP 内存限制失败路径,但底层真实内存分配失败仍可能进入危险流程。
这个配置存在明显的可用性风险:失控请求可能继续占用内存,直到容器或主机 OOM。因此必须同时使用容器或 cgroup 控制资源边界,确保单个异常 Worker 不会拖垮整台服务器。
不要用 RLIMIT_AS 或 RLIMIT_DATA 代替容器内存边界。PHP 官方特别提醒,这两类限制本身可能制造漏洞所需要的内存分配失败条件。
同时应收紧请求体。对不需要文件上传的业务,可以从官方建议的参考值开始:
post_max_size = 8M
upload_max_filesize = 2M必须上传文件的业务可以评估:
post_max_size = 32M
upload_max_filesize = 24M这些数值不是所有业务的通用答案。变更前应统计真实上传大小,否则可能误伤镜像上传、数据导入和支付回调。
Nginx 可在对应 server 或 location 中同步限制:
client_max_body_size 8m;变更前先保存当前配置并进行语法检查:
nginx -T > /var/tmp/nginx-before-php2shell.txt
nginx -t限制还应覆盖没有可信 Content-Length 的请求,以及 multipart 分段数、表单字段数、Header 数量和输入嵌套深度。
缓解 CVE-2026-109619
在请求解析前关闭会话上传进度:
php_admin_value[session.upload_progress.enabled] = 0PHP 8.5 还应临时关闭致命错误回溯:
php_admin_value[fatal_error_backtraces] = 0这些配置应写入 php.ini、FPM Pool 或 Apache 服务端配置。不要依赖应用里的 ini_set(),也不要依赖 .user.ini,因为应用代码执行到这些配置前,漏洞路径可能已经触发。
配置完成后先校验:
php-fpm -tt
nginx -t然后从负载均衡摘除单个实例,等待活动连接排空,再重启全部 PHP Worker。
WAF、IP 白名单、关闭上传入口、限制管理后台都可以作为额外防线,但目前不存在能够证明覆盖全部变体的通用 WAF 规则。
disable_functions 也不能修复 PHP 内核的内存破坏。即使禁用了 exec、system、shell_exec,攻击者仍可能通过其他 PHP 能力读写文件、连接网络或窃取进程凭据。
补丁怎样上线,才不会留下旧 Worker
PHP 上游确认的最低修复版本如下:
| PHP 分支 | 最低上游修复版本 |
|---|---|
| PHP 8.5 | 8.5.12 |
| PHP 8.4 | 8.4.27 |
| PHP 8.3 | 8.3.36 |
| PHP 8.2 | 8.2.35 |
官方源码和 Windows 二进制可以从 PHP Downloads 获取。Linux 生产环境应优先使用发行版或可信 LTS 厂商提供的软件包,不建议直接用源码文件覆盖系统包。
Debian、Ubuntu 环境先检查候选版本:
apt-cache policy php8.4-fpm php8.4-cli php8.4-common确认仓库已经提供安全更新后,再进入变更窗口:
sudo apt-get update
sudo apt-get install \
php8.4-fpm php8.4-cli php8.4-commonRHEL 系发行版先查看可用更新:
dnf check-update 'php*'
dnf info php-fpm确认供应商安全公告和目标版本后执行:
sudo dnf upgrade 'php*'实际环境可能使用模块流、SCL、Remi、源码编译或内部 RPM 仓库。不要为了追求上游版本号,临时混入一个未经验证的软件源。
PHP-FPM 环境完成升级后,对已摘流实例进行完整重启:
sudo systemctl restart php8.4-fpm
sudo systemctl status php8.4-fpm --no-pagerApache mod_php 环境需要重启 Apache:
sudo apachectl configtest
sudo systemctl restart apache2RHEL 系服务名通常是 httpd:
sudo apachectl configtest
sudo systemctl restart httpd仅执行 reload 可能让旧 Worker 在排空请求期间继续存活。这次事件要求确认所有旧 Worker 已经退出,不能只看到 reload 成功就结束变更。
推荐的灰度顺序是:
- 保存软件包、配置、进程和监控快照;
- 从负载均衡摘除一台实例;
- 等待活动请求排空;
- 升级 PHP 及相关扩展;
- 完整重启实际使用的服务单元;
- 验证版本、进程、扩展和核心业务;
- 恢复 1%~5% 流量;
- 观察稳定后逐批扩大。
出现以下情况应停止扩容:
- 核心接口出现新的 5xx;
- PHP-FPM 或 Apache 启动失败;
- 错误率明显高于未升级实例;
- P95、P99 延迟持续恶化;
- PHP 扩展缺失或加载失败;
- 上传、Session、序列化或数据库驱动不兼容;
- 持续出现崩溃、段错误或 Worker 异常退出。
回滚不能简单等于把存在漏洞的旧版本重新开放公网。更安全的方案是保持异常节点摘流,修复配置或扩展兼容问题;如果业务必须暂时运行旧镜像,应隔离网络、限制入口并应用临时缓解措施。
怎样证明线上真的修好了
先检查 CLI 和软件包版本:
php -v
dpkg-query -W \
-f='${Package}\t${Version}\n' \
'php8.4*' 2>/dev/null然后检查处理请求的进程启动时间:
ps -eo pid,lstart,user,cmd \
| grep -E '[p]hp-fpm|[a]pache2|[h]ttpd'所有 Worker 的启动时间都应该晚于升级和重启时间。
在 Linux 上还可以检查进程是否引用已删除的旧二进制或动态库:
for pid in $(pgrep -f 'php-fpm|apache2|httpd'); do
printf '%s\t' "$pid"
readlink "/proc/$pid/exe"
done
sudo lsof +L1 \
| grep -E 'php|php-fpm|apache2|httpd'如果进程路径后出现 (deleted),或者 lsof +L1 显示 PHP 进程仍引用已经替换的动态库,通常说明旧进程没有完全退出。应继续保持节点摘流,确认真实的 systemd 服务单元并完成重启。
不要为了检查 Web SAPI 版本,长期在公网放置 phpinfo() 页面。它可能泄露环境变量、扩展、目录结构和服务器配置。更安全的办法是在受控网络通过现有健康检查返回构建版本,或者创建仅绑定回环地址、验证后立即删除的临时诊断入口。
恢复流量期间至少观察:
- HTTP 4xx、5xx 和应用异常率;
- PHP-FPM 活跃、空闲 Worker 数;
max_children是否频繁耗尽;- Worker 重启、崩溃和段错误数量;
- P95、P99 延迟;
- 登录、上传、支付回调和任务队列;
- 主机或容器内存;
- OOM Kill 和容器重启次数。
只有版本、进程、业务和监控四类证据全部通过,才能认定变更完成。
一个脱敏后的典型处置场景
下面用一个脱敏后的典型场景说明,时间、主机名和数据均为示例。
某团队维护 28 个 PHP 实例,CMDB 记录全部运行 PHP 8.4。应急盘点时,CLI 检查确实显示 8.4,但进程清单暴露出三个例外:两台遗留站点仍通过 Apache mod_php 运行 PHP 7.4,一个定时扩容节点使用了六个月前的旧容器镜像。
团队没有全量重启,而是先把公网 FPM 集群分成四批。第一批摘流两台实例,升级至供应商提供的修复包,重启 PHP-FPM,再使用登录、文件上传和支付回调三组请求验证。
CLI 版本检查通过后,工程师在 lsof +L1 输出中发现一个 FPM 主进程仍引用带 (deleted) 标记的旧动态库。
继续排查发现,这台机器的业务 Pool 由一个模板化 systemd 单元管理,而自动化脚本只重启了默认 php-fpm 服务。软件包虽然已经升级,真正处理流量的 Worker 却仍然运行旧代码。
如果团队只看 php -v,这台服务器就会被错误标记为已修复。最终他们保持节点摘流,重启真实的模板单元,确认旧 Worker 全部退出,再逐步恢复流量。
两台 PHP 7.4 站点没有可确认的供应商软件包,团队先把管理入口限制到 VPN,关闭不必要的上传功能,增加请求体限制,并将实例从公网负载均衡移除。完成兼容测试后,再迁移到受支持的 PHP 8.4 镜像。
这条证据链很清楚:CMDB给出初始范围,进程列表发现遗漏,lsof 证明旧库仍被加载,systemd 单元解释了自动化失效,灰度流量和业务监控验证了最终修复。
EOL 版本怎么办
PHP 8.1 及更早分支已经退出 PHP 项目的常规安全支持。部分 Linux 发行版和商业 LTS 厂商可能提供反向移植补丁,应优先使用经过签名、具有正式安全公告的软件包。
考虑到此次漏洞的严重性,PHP 安全团队还提供了三个最小源码回移分支:
这些分支不代表旧版 PHP 恢复了官方生命周期,也不是可以直接安装的通用发行版软件包,主要面向本来就具备源码构建和维护能力的团队。
如果生产环境没有成熟的自编译、测试、签名和发布链路,不建议在重大漏洞窗口期临时切换到手工编译。更稳妥的处理顺序是:
- 查询操作系统或商业 LTS 厂商是否提供反向移植补丁;
- 核对软件包签名、构建来源和安全公告;
- 没有可信补丁时,摘除公网流量或隔离服务;
- 应用临时缓解措施争取迁移时间;
- 完成兼容测试后升级到受支持分支。
今晚可以直接照着执行的检查清单
- 务必同时检查 CLI、FPM、mod_php、php-cgi 和容器版本,因为
php -v不能代表线上 Web SAPI。 - 不要仅按上游版本字符串判断发行版软件包,反向移植补丁需要结合供应商公告和 Release 字段确认。
- 不要把更新 Composer、WordPress 或 Laravel 当成此次修复,漏洞位于 PHP 核心请求处理路径。
- 不要把 WAF 或
disable_functions当成完整缓解,它们无法消除内存破坏和状态残留。 - 设置
memory_limit=-1时,必须评估真实 OOM 和拒绝服务风险,并用容器或 cgroup 控制故障边界。 - 禁止用
RLIMIT_AS或RLIMIT_DATA代替官方建议的资源隔离,它们可能制造危险的内存分配失败。 - 关闭
session.upload_progress.enabled后务必重启每个 Worker,否则旧进程仍保留原配置和内部状态。 - 不要只 reload 后立即宣布完成,应核对进程启动时间以及是否仍引用
(deleted)的旧文件。 - 不要在未摘流时批量重启全部节点,否则一次配置或扩展兼容问题就可能演变成全站中断。
- 回滚不能等于把有漏洞的旧版本重新开放公网;无法升级的节点应隔离、限流并尽快迁移。
- 不要把单条内存耗尽或致命错误日志直接认定为攻击,必须结合请求、进程、外连和文件变化建立证据链。
- 务必保留升级前后的软件包、配置、进程、日志和监控快照,否则无法证明修复是否完整。
- 不要长期暴露
phpinfo()页面,其中可能包含路径、扩展、环境变量和服务器配置。 - EOL 分支如果没有可信厂商补丁,必须制定迁移计划;临时隔离只能争取时间。
漏洞参考文档
下面按可信度和用途整理相关资料。版本范围、漏洞原理、缓解措施及修复版本,应以 PHP 官方公告和官方 GitHub Security Advisory 为准。
官方一手资料
PHP 官方安全公告:Critical remote code execution vulnerabilities, update now
汇总受影响运行方式、漏洞原理、上游修复版本、临时缓解措施和旧分支处置说明。
CVE-2026-109618 / GHSA-w8hw-w9fx-j79w
CVE-2026-109618 的官方技术公告,重点解释 OOM 处理、请求数据部分初始化和清理阶段内存破坏。
CVE-2026-109619 / GHSA-6wmf-4rv5-p3rp
CVE-2026-109619 的官方技术公告,重点解释请求状态释放后的残留、跨请求复用及相关缓解配置。
用于获取官方源码和 Windows 二进制。Linux 生产环境应优先核对发行版或 LTS 厂商的软件包。
用于确认不同 PHP 分支的主动支持、安全支持和停止维护时间。
用于核对上游版本变更。发行版使用反向移植补丁时,还需查看发行版自己的安全公告。
CVE 数据库
漏洞刚发布时,NVD 的 CPE、评分和受影响版本字段可能仍在完善。页面信息与 PHP 官方公告暂时不一致时,应优先采用 PHP 官方结论,并持续关注数据库更新。
事件背景材料
这两篇文章适合了解漏洞披露前后的传播时间线,但不能代替官方技术公告。补丁发布前流传的“影响 PHP 5 以来全部版本”“默认配置均可直接利用”“漏洞位于某个特定解析模块”等内容,当时包含第三方转述或技术推测,引用时必须标明时间状态。
本文文档核对时间为 2026 年 10 月 21 日。漏洞公告、供应商补丁和检测规则仍可能更新,执行生产变更前应重新检查 PHP 官方公告,以及所用操作系统、容器镜像和 LTS 厂商的最新安全通知。
这次 PHP2Shell 事件真正考验的,不是团队能否在新闻出现后迅速执行一条升级命令,而是资产发现、变更灰度、进程验证和应急回滚是否形成了完整链路。
现在最值得立即执行的动作,是导出所有处理 Web 请求的 PHP 实例和镜像清单,将它们与四个上游修复版本或供应商安全公告逐一比对,并指定责任人确认每个旧 Worker 已经退出。
如果这份排查手册对你有帮助,欢迎点赞、在看并转发给仍在维护 PHP 服务的同事。关键漏洞面前,可靠的资产清单和可验证的升级流程,比任何“祖传经验”都更有价值。
公众号:耕云躬行录
个人博客:躬行笔记