昨天 PHP 曝出编号为 CVE-2026-109618 和 CVE-2026-109619 的两个严重远程代码执行(RCE)漏洞,CVSS 评分达到 9.2 分。根据官方预告以及欧盟计算机应急响应小组(CERT-EU)通报,补丁窗口定于北京时间 10 月 21 日 22 时释出。

这一消息在开发者社区引发广泛讨论,甚至惊动了日本首相高市早苗(哪个开发在这个地球上有这个待遇,PHP完胜)。

在 Hacker News 热帖的数百条评论中,工程团队的讨论焦点集中在两件事上。其一是各发行版构建、验证与分发补丁包存在时间差;其二是大量仍在运行的旧版 PHP 项目缺乏上游官方补丁支持,直接跨大版本升级又面临极高的代码改造与扩展兼容风险。
目前 ServBay 已经针对此次 PHP 漏洞完成了安全补丁更新,并且同步发布了对旧版 PHP 的补丁支持。对于维护多版本项目或历史遗留系统的开发者而言,补丁包就绪解决了弹药问题,而接下来最关键的动作是核实更新后的二进制文件是否真正运行在每一个目标站点上。
面对 PHP 安全补丁更新,终端执行升级命令并提示成功,不代表线上网站与后台任务已经脱离风险。同一台服务器或开发机存在多个 PHP 版本时,命令行(CLI)、Web 请求处理进程(PHP-FPM)以及常驻内存队列各自加载独立的二进制文件与配置文件。只有沿着包已安装、站点已选择、进程已切换、回归已通过这四个环节逐一留存运行证据,才能确认新补丁真正接管了业务流量。
为什么安装不等于运行
许多开发者习惯借助 AI 编程助手生成升级指令。在终端运行包升级命令后,看到命令行输出最新版本号,便以为修复工作已经结束。这种判断在多版本共存环境中极易产生漏防。

一台机器上的 PHP 至少存在三条相互独立的执行链路。
终端命令行入口(CLI)
开发者在 SSH 会话或本地终端输入 php -v 时,操作系统根据当前用户的 PATH 环境变量查找可执行文件。安装包更新后,默认软链接可能指向了新版本,但这仅改变当前终端会话的命令行为,与对外提供 HTTP 服务的进程没有直接关联。
Web 服务器转发入口(PHP-FPM)
Nginx 与 Apache 处理动态请求时,通过 fastcgi_pass 指令将请求转发给后端的 PHP-FPM 监听端口(如 127.0.0.1:9000)或 Unix Domain Socket(如 /run/php/php8.2-fpm.sock)。
这里存在两种典型的脱节情况。
第一种是跨分支共存,例如系统安装了新版 PHP 8.3,但站点的 Nginx 配置文件依然连接 php8.1-fpm.sock,外部流量继续由旧分支处理。
第二种是同分支小版本更新,虽然磁盘上的二进制文件已被替换,但正在运行的 php-fpm Master 进程如果未收到重载或重启信号,内存中驻留的 Worker 进程仍会继续执行旧版程序。
后台常驻进程(Queue Worker)
采用 Laravel Queue、Symfony Messenger、Swoole 或 RoadRunner 的项目,后台进程由 Supervisor 或 Systemd 托管并长年驻留内存。即便命令行和 Web 端的 PHP-FPM 都完成了更新,只要没有显式向常驻 Worker 发送重启指令,队列消费者与定时任务依然在旧版本的内存镜像中运行。
四步闭环核验流程
要确切证明补丁已经在生产或测试环境中生效,不能只看安装成功提示,必须按四个步骤采集运行态证据。

第一步 盘点本机已安装的 PHP 版本
升级操作前后,在终端排查机器上存在的所有 PHP CLI 与 FPM 二进制文件路径,摸清当前环境底数。
bash
# 查看环境变量中所有名为 php 的可执行文件
which -a php
# 查看当前默认 CLI 的详细版本与构建时间
php -v
# 检索系统中所有正在运行的 php-fpm 进程及对应配置文件
ps aux | grep php-fpm
若使用 Linux 系统包管理器,可通过以下命令核查已安装的包名与具体构建版本号,确认包含 CVE-2026-109618 与 CVE-2026-109619 修复补丁的软件包已下载到位。
bash
# Debian / Ubuntu 核查已安装的 PHP 相关包版本
dpkg -l | grep -E "php[0-9]+.[0-9]+-(cli|fpm)"
# RHEL / Rocky / AlmaLinux 核查已安装的 PHP 包
rpm -qa | grep -E "php-(cli|fpm)"
第二步 核对目标站点的 FastCGI 绑定配置
不要假设升级命令会自动修改 Web 服务器配置。打开目标站点的 Nginx 或 Apache 虚拟主机配置文件,定位 fastcgi_pass 或 SetHandler 指向的真实地址。
bash
# 在 Nginx 配置目录中检索所有站点的 fastcgi_pass 指向
grep -rn "fastcgi_pass" /etc/nginx/conf.d/ /etc/nginx/sites-enabled/
拿到 Socket 路径或端口号后,反查当前监听该地址的进程 PID 及其对应的二进制文件真实路径。
bash
# 通过 Socket 或端口查找对应的 php-fpm 主进程 PID
sudo lsof /run/php/php8.3-fpm.sock
# 查看该 PID 实际加载的二进制文件磁盘路径(将 12345 替换为实际 PID)
sudo ls -l /proc/12345/exe
在 Linux 系统中,如果磁盘上的二进制文件已被更新,而进程尚未重启,/proc/PID/exe 路径末尾会显示 (deleted) 标记。这就是磁盘已更新但内存仍在跑旧版的直接证据。
第三步 重启服务与后台常驻进程
确认站点配置指向正确的 PHP 分支后,依次重启对应的 PHP-FPM 服务与后台队列进程。
bash
# 重启对应版本的 PHP-FPM 服务(按实际版本号替换)
sudo systemctl restart php8.3-fpm
# 核对服务启动时间戳与新的 Master PID
sudo systemctl status php8.3-fpm
对于包含异步队列或常驻内存框架的 PHP 项目,接着执行框架层与进程管理器的刷新指令。
bash
# Laravel 项目通知所有队列 Worker 在当前任务结束后退出重连
php /var/www/project/artisan queue:restart
# 若使用 Supervisor 托管常驻任务,重启对应进程组
sudo supervisorctl restart all
第四步 受控运行态验证与业务回归
为验证 Web 端实际生效的 PHP 版本,切忌在网站根目录长期放置公开可访问的 phpinfo.php 文件,以免向公网泄露服务器环境变量与路径结构。稳妥的做法是在仅限本机回环地址(127.0.0.1)访问的受控条件下,使用一次性最小化脚本输出运行态版本与 SAPI 信息,验证完毕立即删除。
bash
# 1. 创建仅本机请求可读取的临时核验脚本
cat << 'EOF' > /var/www/project/public/.patch_check.php
<?php
if ($_SERVER['REMOTE_ADDR'] !== '127.0.0.1' && $_SERVER['REMOTE_ADDR'] !== '::1') {
http_response_code(403);
exit('Forbidden');
}
header('Content-Type: application/json');
echo json_encode([
'sapi' => PHP_SAPI,
'version' => PHP_VERSION,
'version_id' => PHP_VERSION_ID,
'ini_loaded' => php_ini_loaded_file(),
'opcache_enabled' => function_exists('opcache_get_status') && opcache_get_status(false) !== false,
], JSON_PRETTY_PRINT);
EOF
# 2. 在服务器本机通过 curl 指定域名发起核验请求
curl -s -H "Host: example.com" http://127.0.0.1/.patch_check.php
# 3. 核验版本号与预期的补丁版本一致后,立即销毁临时文件
rm -f /var/www/project/public/.patch_check.php
当输出的 JSON 中 sapi 显示为 fpm-fcgi,且 version 与修复版本号一致时,说明 Web 链路已切实切换成功。随后检查项目依赖的核心扩展(如 redis、pdo_mysql、bcmath、opcache)是否正常加载,并运行核心接口回归测试。
旧版补丁与按项目切换
在实际业务维护中,很多团队面临两难处境。一方面手头同时维护着跑在 PHP 8.3 的新项目与跑在 PHP 7.x 甚至更早版本的历史遗留系统;另一方面,许多传统软件源只为活跃分支提供更新,旧项目如果强行跨大版本升级,大量历史语法与老扩展会直接报错停摆。
针对这一痛点,ServBay 此次不仅第一时间为当前主流 PHP 分支打上了安全补丁,还专门针对旧版 PHP 发布了漏洞修复支持。配合 ServBay 按项目选择 PHP 版本的隔离机制,维护人员可以在不破坏旧项目代码兼容性的前提下完成安全加固。

在多版本混合环境中,这种治理方式解决了三个工程难题。
-
旧版项目免重构打补丁: 依赖旧版 PHP 的历史系统无需冒险跨大版本迁移,直接在原有分支上更新已集成安全补丁的软件包即可修复漏洞,避开了因升级导致业务中断的风险。
-
站点与运行版本清晰绑定: 每个站点在面板中明确指定各自使用的 PHP 版本,消除了手写 Nginx
fastcgi_pass容易连错端口或 Socket 的人为失误。 -
升级切换与运行验证合一: 在软件包面板将对应 PHP 分支更新至补丁版本并重启服务后,同样可以通过受控请求核对站点实际运行的进程状态,实现从安装、切换到验证的完整闭环。
升级核验清单与常见问答
为了方便日常维护与团队协作,可将整套验证动作固定为以下标准检查表。每次执行 PHP 安全更新或版本切换时,逐项核对确认。
多版本 PHP 环境升级核验表
| 核验阶段 | 检查项目 | 验证方式与工具 | 达标判据 |
|---|---|---|---|
| 包已安装 | 补丁包版本核对 | 查阅官方安全公告或 ServBay 软件包面板 | 主流版本或旧版支持包已更新至含补丁版 |
| 包已安装 | CLI 入口版本核对 | which -a php 与 php -v |
终端路径指向已打补丁的二进制文件 |
| 点已选择 | Web 转发配置核对 | 检查 Nginx fastcgi_pass 或站点绑定设置 |
站点绑定的 PHP 版本与目标补丁分支一致 |
| 进程已切换 | PHP-FPM 进程刷新 | 重启 FPM 服务并检查 /proc/PID/exe |
Master PID 已更新,无 (deleted) 残留 |
| 进程已切换 | 常驻队列进程刷新 | artisan queue:restart / supervisorctl |
所有 Worker 进程启动时间晚于升级时间 |
| 回归已通过 | Web 运行态证据采集 | 本机受控访问最小化脚本(阅后即焚) | 返回的 PHP_VERSION 与补丁版本完全一致 |
| 回归已通过 | 扩展与业务接口回归 | php -m 对比及核心业务接口自动化测试 |
扩展无缺失,日志无新增报错,响应正常 |
常见问题解答(FAQ)
为什么终端执行 php -v 显示已经是最新版本,但网页端输出的 PHP_VERSION 还是旧版本?
因为终端命令调用的是系统 PATH 环境变量指向的 PHP CLI 程序,而网页请求由 Nginx 或 Apache 转发给后台的 PHP-FPM 守护进程处理。如果未重启 PHP-FPM 服务,或者站点的 FastCGI 配置仍然绑定在旧版本的 Socket 上,网页端就会一直运行旧版本。
正在维护的旧项目无法升级到 PHP 8.3,面对此次高危漏洞该如何修复?
跨大版本升级旧项目容易引发语法不兼容和第三方库报错。对于无法立即重构的历史项目,可以使用像 ServBay 这样已为旧版 PHP 同步提供安全补丁支持的环境,直接在原版本分支上更新补丁包并重启服务,既能修补高危漏洞,又能保持原有业务逻辑与扩展正常运转。
同分支更新安全补丁后,为什么后台队列任务还是没有生效?
像 Laravel Queue、Swoole 或 वर्कerman 这类后台任务属于常驻内存进程,在启动时已将旧版 PHP 解释器加载到内存中。更新磁盘上的 PHP 软件包或重启 PHP-FPM 都不会打断常驻队列进程,必须额外执行 php artisan queue:restart 或通过 Supervisor 重启对应 Worker 进程组,才能让后台任务加载修补后的新版本。
总结
处理 CVE-2026-109618 与 CVE-2026-109619 这类高危漏洞时,下载并安装新版本只是起点,确认线上进程真正加载了新补丁才是终点。在多版本共存的工程环境中,单独依赖一条 php -v 命令极易被表面结果误导。
稳妥的升级策略分为两步。在环境供给侧,借助 ServBay 这类已为主流版本及历史旧版同步提供安全补丁、且支持按项目绑定 PHP 版本的工具,解决老项目无法跨版本升级与多站点配置混乱的难题;在上线核验侧,严格执行包已安装、站点已选择、进程已切换、回归已通过的四步检查清单,分别确认 CLI、PHP-FPM 与常驻队列的真实进程状态。将这套核验动作沉淀为团队日常操作规范,后续面对任何运行时安全更新都能做到有据可查、平稳落地。规矩要立住,写起代码来才能不烧心。