PHP2Shell 自查:按来源确认你的 PHP 是否已修复
php -v 显示的版本号不能证明已修复。按 apt、宝塔、1Panel 和 Docker 四种来源,逐个确认每个站点实际调用的 PHP 是否包含 CVE-2026-109618/109619 的补丁,再重启所有 PHP 进程。
先说结论:php -v 的版本号不是判断标准
要确认 PHP 是否已修复 PHP2Shell(CVE-2026-109618 和 CVE-2026-109619),只看 php -v 不够。正确的做法是:先找出每个站点实际调用的那个 PHP,再按它的来源去核对。apt 装的看软件包 changelog 和包版本,宝塔和 1Panel 看面板里的版本,Docker 看镜像标签。核对完还要重启所有 PHP 进程,否则旧代码还留在内存里继续运行。
php -v 不可靠,有两个原因。第一,Ubuntu 打安全补丁时不改上游版本号。Ubuntu 24.04 的修复版是 8.3.6-0ubuntu0.24.04.13,php -v 仍然显示 8.3.6。这个数字比上游修复版 8.3.36 小得多,但它已经修好了。第二,一台 VPS 上经常同时有 apt 装的 PHP、宝塔编译的 PHP 和容器里的 PHP。你在 shell 里敲 php -v,看到的只是 PATH 里排在最前面的那一个,它不一定是正在处理网页请求的那一个。
PHP2Shell 是什么,哪些 PHP 受影响
PHP 官方在 2026 年 10 月 21 日于 php.net 发布安全公告,修复两个可以导致未经认证的远程代码执行(RCE,remote code execution)的严重漏洞。按公告的描述:
- CVE-2026-109618:内存不足(OOM,out of memory)的处理可能在请求数据只初始化了一部分时打断 PHP,之后的请求清理会把这些无效数据当成有效数据。
- CVE-2026-109619:属于某个请求的内部状态,在它背后的内存被释放后仍然残留。同一个 worker 处理的下一个请求,可能在正常初始化重置它之前用到这份过期状态。
上游修复版本是 8.5.12、8.4.27、8.3.36、8.2.35,低于这些版本的上游 PHP 都受影响。公告点名的 SAPI(server API,PHP 和 Web 服务器之间的接口)包括 PHP-FPM、Apache mod_php、php-cgi、FrankenPHP 和 LiteSpeed,也就是所有处理网页请求的 PHP。官方认为 mod_php 的风险最高,因为已经有演示表明,一个未认证的请求可以在任何应用代码运行之前触发攻击。所以,你的 WordPress 插件是不是最新版,和这个漏洞没有关系。
PHP 8.1 及更早的版本已经不再由 PHP 项目更新。公告让这些用户联系发行版或 LTS 厂商(有些厂商提供回移补丁),或者升级到仍受支持的版本。如果你打算借这次机会升级,注意 PHP 8.2 会在 2026 年 12 月 31 日结束支持,之后不再有安全修复。
美国 CISA 在 10 月 21 日的通告里说,截至发布时还没有观察到这两个漏洞被利用。同一份通告也建议:无法更新的 PHP 系统应当先下线。没有观察到利用,不是可以拖延的理由。
第一步:找出机器上所有正在运行的 PHP
不要先问"我装了哪个 PHP",要先问"现在跑着哪些 PHP"。下面这条命令列出所有名字里带 php 的进程:
ps -eo pid,user,lstart,args | grep -i '[p]hp'lstart 是进程的启动时间,第四步判断"更新后有没有重启"要用到它。[p]hp 这种写法让 grep 不会匹配到它自己这一行。PHP-FPM 的输出里通常有一个 master process 和若干 pool 进程,记下每个 master 的 PID。
然后查每个 master 实际执行的是哪个文件,再问 dpkg 这个文件属于哪个软件包:
sudo readlink /proc/<PID>/exe
dpkg -S "$(sudo readlink /proc/<PID>/exe)"如果 dpkg -S 打印出一个包名,比如 php8.3-fpm,这个 PHP 就是 apt 装的。如果它报 no path found matching pattern,说明这个文件不属于任何 apt 包。它可能是宝塔或 LNMP 一键包编译安装的,也可能来自其他地方。这一点很重要:apt upgrade 只升级 apt 自己管理的文件,所以它永远不会更新这些 PHP。
Docker 容器里的进程也会出现在宿主机的 ps 列表里,但它的文件路径是容器内部的路径,宿主机上的 dpkg 认不出它。先用下面的命令列出所有容器和它们的镜像,容器里的 PHP 留到第三步按 Docker 的方法检查:
docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Ports}}'第二步:每个站点调用的是哪一个 PHP
找到所有 PHP 之后,还要知道每个站点用的是哪一个。答案写在 Web 服务器的配置里。
Nginx:看 fastcgi_pass 指向哪里
Nginx 自己不执行 PHP,它用 fastcgi_pass 把请求交给一个 FastCGI 服务器。这个地址可以是 127.0.0.1:9000 这样的 TCP 地址,也可以是 unix:/路径/xxx.sock 这样的 Unix 套接字。nginx -T 会先检查配置,再把所有被加载的配置文件(包括 include 进来的文件)打印出来:
sudo nginx -T 2>/dev/null | grep -E '# configuration file|server_name|fastcgi_pass'有些面板把 fastcgi_pass 写在单独的文件里,再用 include 引入。nginx -T 会把这些文件一起打印出来,所以这条命令仍然能看到它。如果系统提示找不到 nginx 命令,说明 nginx 不在 PATH 里,可以直接用正在运行的那个程序:
sudo "$(sudo readlink /proc/$(pgrep -o nginx)/exe)" -T 2>/dev/null | grep -E '# configuration file|server_name|fastcgi_pass'拿到地址后,查是哪个进程在监听它:
sudo ss -xlp | grep -i php
sudo ss -ltnp | grep -i php第一条列出 Unix 套接字,第二条列出 TCP 端口,两者都会显示监听进程的名字和 PID。把套接字路径或端口和 fastcgi_pass 对上,再拿 PID 回到第一步,就知道这个站点用的是哪个 PHP 了。apt 装的 PHP-FPM,每个池(pool)的监听地址写在 listen 这一行:
grep -R '^listen' /etc/php/*/fpm/pool.d/Apache:mod_php 还是转发给 FPM
sudo apache2ctl -M 2>/dev/null | grep -Ei 'php|proxy_fcgi'
sudo apache2ctl -S-M 列出已加载的模块,-S 列出虚拟主机。如果模块列表里有名字带 php 的模块,说明 PHP 以 mod_php 的方式直接跑在 Apache 进程里。这正是公告认为风险最高的方式。如果只有 proxy_fcgi_module,说明 Apache 把 PHP 请求转发给了 PHP-FPM,转发地址写在 SetHandler 里:
sudo grep -Ri 'SetHandler' /etc/apache2/两种方式的区别在 在 Ubuntu 24.04 上搭建 LAMP 环境 里有完整说明。
容器:看端口和反向代理
如果 Nginx 的 fastcgi_pass 指向 127.0.0.1:9000,而 ss 显示监听这个端口的进程是 docker-proxy,那么 PHP 跑在容器里。第一步 docker ps 输出的 Ports 列会告诉你是哪个容器。也有整个站点(Nginx 加 PHP)都在容器里的情况,这时宿主机的 Nginx 只做反向代理,PHP 同样按 Docker 的方法检查。
第三步:按来源核对是否已修复
apt 安装的 PHP(Ubuntu)
Ubuntu 在 10 月 21 日发布了 USN-8948-1,覆盖 php-fpm、libapache2-mod-php、php-cgi 和 php-cli。ubuntu.com 的 CVE 页面列出的修复版本是:
- Ubuntu 26.04:php8.5
8.5.4-0ubuntu1.5 - Ubuntu 24.04:php8.3
8.3.6-0ubuntu0.24.04.13 - Ubuntu 22.04:php8.1
8.1.2-1ubuntu2.28 - Ubuntu 26.10:php8.5
8.5.9-0ubuntu2.1
先看装了哪些 PHP 包和它们的版本:
dpkg-query -W -f='${Package} ${Version}\n' 'php*-fpm' 'php*-cgi' 'php*-cli' 'libapache2-mod-php*' 2>/dev/null再用 dpkg --compare-versions 和修复版比较。它懂 Debian 的版本号规则,所以不会被 8.3.6 小于 8.3.36 这种表面数字误导。下面以 24.04 为例,其他版本把包名和修复版换成上面列表里对应的值:
dpkg --compare-versions "$(dpkg-query -W -f='${Version}' php8.3-fpm)" ge 8.3.6-0ubuntu0.24.04.13 && echo 已修复 || echo 未修复也可以看软件包的 changelog。apt-get changelog 默认显示已安装版本的 changelog,需要服务器能联网下载:
apt-get changelog php8.3-fpm | grep -E 'CVE-2026-1096(18|19)'在 24.04 上,8.3.6-0ubuntu0.24.04.13 的 changelog 条目写着 SECURITY UPDATE: Remote RCE via state corruption issue,并列出了这两个 CVE 编号,所以 grep 应该打印两行。其他 Ubuntu 版本也可以这样查。如果 grep 没有输出,以上面的版本比较结果为准。
没修复就更新。USN 给出的更新方法就是普通的系统更新:
sudo apt update && sudo apt upgrade注意:现在新装的 Ubuntu 24.04 从官方源安装 php8.3-fpm,装到的直接就是修复版。如果你开着自动安全更新,包可能已经装好了,配置方法见 Ubuntu 自动安全更新的配置方法。但包装好了,不等于正在运行的进程已经换成新代码,第四步会解释原因。
Ubuntu 20.04 及更早的版本只能通过 Ubuntu Pro 获得修复。按 CVE-2026-109618 页面:20.04 的 php7.4 修复版是 7.4.3-4ubuntu2.29+esm5,18.04 的 php7.2 是 7.2.24-0ubuntu0.18.04.17+esm14。16.04 的 php7.0 和 14.04 的 php5 还需要 Legacy Support 附加服务。没开 Pro 的话,apt upgrade 拿不到这些包。开启方法见 服务器上的 Ubuntu Pro 说明。
如果你用的是第三方 PPA 而不是 Ubuntu 官方的 PHP 包,版本号规则由 PPA 自己决定,请以该 PPA 的说明为准。
宝塔面板
宝塔在 10 月 21 日发布了紧急通知(docs.bt.cn,10 月 22 日更新)。宝塔的 PHP 由面板自己安装,不是 apt 包,所以只能在面板里升级。按通知的内容:
- 8.2.35、8.3.36、8.4.27、8.5.12 是官方修复版本,面板里可以直接升级。
- 7.4.34、8.0.31、8.1.35 由宝塔为 Linux 面板打包,宝塔说明其中包含这两个漏洞的修复。
- 7.0 到 7.3 由宝塔自行构建为 7.0.34、7.1.34、7.2.35、7.3.34。宝塔说明这是以 PHP 7.4 的官方补丁为基础逐行比对、回移适配的。这是宝塔自己的说法,外部无法独立核实。
- 通知没有提到 PHP 5.6。在宝塔给出说明之前,把 5.6 当作没有修复,尽快把站点迁到受支持的版本。
- Windows 面板上 8.1 及更早版本目前没有修复版,宝塔只给出了改配置、禁用相关功能的临时缓解方法。
升级路径:打开 软件商店,选择 已安装,先点右上角的 更新软件列表 / 支付状态 获取最新信息,再对站点正在使用的 PHP 版本点更新,升级完成后确认版本号。宝塔的通知提醒,升级会重启对应的 PHP 服务,站点会短暂不可用,所以建议在业务低峰期操作。
确认版本请看面板 已安装 页面里的版本号,不要拿 shell 里的 php -v 当证据。原因在第一节:shell 里的 php 可能是 apt 装的另一个 PHP。宝塔可以同时安装多个 PHP 版本,不同站点可能用不同的版本,所以每个版本都要检查。
1Panel
1Panel 的 PHP 运行环境一般跑在容器里。截至 10 月 22 日,在 1Panel 的 GitHub issue #14149 里,维护者在 10 月 21 日回复说需要等官方 PHP 镜像更新。同一天另一个 issue(#14150)里有用户反馈应用商店里只有 8.5.11。这些是 issue 里的回复,不是正式的安全公告,情况可能已经变化,请以 1Panel 官方渠道的最新说明为准。
核对方法有两步。先在面板的 PHP 运行环境列表里看每个运行环境的版本,再进容器确认(容器名用第一步 docker ps 的输出):
docker exec <容器名> php -v对 8.2 及以上的版本,容器里显示的版本号应当不低于上游修复版,也就是 8.2.35、8.3.36、8.4.27 或 8.5.12。8.1 及更早的版本情况不同:Docker 官方的 php 镜像目前只提供 8.2 到 8.5 的标签,没有 8.1、8.0 和 7.4。上游为 7.4、8.0 和 8.1 提供的是最小化的源码回移补丁(php-src 里的单独分支),供自行从源码编译、或者没有 LTS 厂商支持的用户使用,Docker 官方镜像里没有对应的标签。所以这类运行环境里的版本号无论显示多少,都证明不了什么,只能依据 1Panel 自己的说明。如果找不到说明,最稳妥的做法是把站点迁到 8.2 以上。
1Panel 的运行环境也可以选"本地"类型,它使用服务器上已安装的 php-fpm。这种情况回到上面的 apt 一节检查。
Docker 镜像
Docker 官方 php 镜像已经发布了 8.5.12、8.4.27、8.3.36、8.2.35 这些标签。官方镜像的标签就是上游版本号,所以这里 php -v 是有意义的:
docker ps --format 'table {{.Names}}\t{{.Image}}'
docker exec <容器名> php -v要特别注意 php:8.3-fpm 这类浮动标签。标签只是一个名字,它指向的是你上次拉取时的那个镜像。上游发布新版后,你本地的 php:8.3-fpm 不会自己变,必须重新拉取并重建容器:
docker compose pull
docker compose up -d如果你的镜像是用自己的 Dockerfile 从 FROM php:... 构建的,pull 不会更新它。要加 --pull 重新构建,这样构建时会先拉取最新的基础镜像:
docker compose build --pull
docker compose up -d有些镜像不用官方 php 镜像,而是在 Debian 或 Alpine 基础镜像里用系统包管理器装 PHP。这种镜像的版本号遵循发行版自己的规则,要按那个发行版的安全公告核对,不能直接和上游版本号比。
第四步:重启所有 PHP 进程,这一步最常被跳过
软件包更新只替换了磁盘上的文件。已经在运行的进程,用的还是它启动时载入内存的旧代码,直到它重启为止。所以"包已经是修复版"和"服务已经修复"是两件事。USN 只说标准系统更新会完成必要的改动,没有提重启。PHP 公告则在缓解措施里反复要求重启每一个 worker。
还有一个常见误解:更新 WordPress、Laravel 或 Composer 依赖,只更新了用 PHP 写的应用代码,PHP 解释器本身一个字节也没变。
先列出相关的服务:
systemctl list-units --type=service --all 'php*' 'apache2*'然后重启。下面以 Ubuntu 24.04 为例,服务名用上一条命令的输出:
sudo systemctl restart php8.3-fpm
sudo systemctl restart apache2用 mod_php 的话,PHP 跑在 Apache 的进程里,所以必须重启 Apache。宝塔的 PHP 在面板里重启对应的 PHP 服务。容器在 docker compose up -d 重建后会用新镜像启动。
长时间运行的 PHP 进程也要处理,比如 Supervisor 管理的队列 worker、Laravel 的 php artisan queue:work 或 Horizon。公告列出的受影响 SAPI 是处理网页请求的那几种,CLI 不在其中。但这些进程同样还在用旧的二进制文件,统一重启最省事。第二条命令要在 Laravel 项目目录里运行:
sudo supervisorctl restart all
php artisan queue:restart最后验证。重新跑第一步的 ps 命令,每个 PHP 进程的 lstart 都应该晚于包的升级时间。apt 的升级时间可以在 dpkg 日志里查:
grep ' upgrade php' /var/log/dpkg.log | tail -n 5如果某个进程的启动时间早于升级时间,它就还在跑旧代码,需要再重启一次。
暂时无法更新时,缓解措施能做到什么
PHP 公告说得很清楚:只有更新才是完整的修复,下面这些措施只能减少特定的攻击路径,并不能让系统变安全。宝塔的缓解说明也是同样的立场。它们只适合在更新前的短时间内使用。
针对 CVE-2026-109619:
- 设置
session.upload_progress.enabled=0,然后重启每一个 worker。这个选项默认是 1(开启)。公告说它能挡住已确认的 session 路径,但挡不住其他变种。 - PHP 8.5 还要设置
fatal_error_backtraces=0,同样要重启每一个 worker。它只挡住已确认的致命错误回溯路径。
针对 CVE-2026-109618:
- 设置
memory_limit=-1,最好用应用无法覆盖的方式设置,例如 FPM 池里的php_admin_value。这和平时"限制内存"的建议正好相反。公告的解释是:它去掉了可以被请求控制的 PHP 内存限制失败,但底层的内存分配失败仍然可能走到有漏洞的代码路径。memory_limit的默认值是 128M。宝塔的说明在这一项旁边提醒:该配置会取消 PHP 自身的内存限制,不建议普通用户在缺少资源隔离的情况下直接使用。 - 把
post_max_size调低:不需要上传文件的应用设为 8M,需要上传的设为 32M,并让upload_max_filesize小于post_max_size。post_max_size的默认值是 8M。 - 用容器或 cgroup 的内存限制作为可用性的边界。不要用
RLIMIT_AS或RLIMIT_DATA代替,公告说它们反而可能触发有漏洞的分配失败。
这些设置必须写在 php.ini、FPM 池或服务器配置里,因为它们要在请求解析之前生效。不要依赖 ini_set() 或 FPM 的 .user.ini。FPM 池里的写法如下:
php_admin_value[memory_limit] = -1
php_admin_value[post_max_size] = 8M
php_admin_value[session.upload_progress.enabled] = 0用 php_admin_value 设置的值,应用无法用 ini_set() 改回去。改完后检查有没有别的池、目录或应用配置把有限的 memory_limit 又设了回去,然后重启 PHP-FPM。
打完补丁后,memory_limit 和 post_max_size 要不要改
有报道说这次修复给 PHP 加了一个会直接结束进程的硬性内存上限。GHSA-w8hw-w9fx-j79w 的原文是这样描述修复的:补丁去掉了 OOM 时使用的 longjmp(),改为在下一个 VM 安全点触发一个延迟的、平稳的软 OOM;如果内存用量达到第二个更高的阈值,就用 _exit() 立即硬性结束。PHP 原有的内存限制机制仍然保留。
公告没有要求打完补丁后重新调整这两个参数。不过有一件事要做:如果你在更新前把 memory_limit 临时设成了 -1,补丁装好、进程重启之后,应该把它改回原来的有限值。-1 只是更新前的权宜之计。它意味着单个请求可以用掉整台机器的内存,在小内存 VPS 上很危险。post_max_size 如果为了缓解调得过低,导致正常的上传失败,也可以恢复成业务需要的值。
另外,如果补丁之后你发现 PHP-FPM 子进程在内存压力大时被直接结束,先检查 memory_limit 和池的大小是否和机器内存匹配,计算方法见 小内存 VPS 上的 PHP-FPM 进程池大小计算。
修完 PHP 之后
这次排查最有价值的产出是一张清单:机器上有几个 PHP,各自来自哪里,哪个站点用哪个。把它写下来,下次公告一出,你只需要几分钟就能确认。想对整台服务器做一次已知漏洞检查,而不只是 PHP,可以接着看 检查服务器上的已知 CVE 漏洞。
FAQ
php -v 显示 8.3.6,比 8.3.36 小,是不是还没修复?
在 Ubuntu 上不一定。Ubuntu 修补安全漏洞时保留上游版本号,只改包版本的后缀。Ubuntu 24.04 的修复版是 8.3.6-0ubuntu0.24.04.13,php -v 仍然显示 8.3.6。用 dpkg --compare-versions 比较完整的包版本,或者用 apt-get changelog php8.3-fpm | grep CVE-2026-109618 查 changelog,才是有效的检查。
宝塔面板里升级了 PHP,还需要做什么?
在 软件商店 的 已安装 页面确认每个 PHP 版本都已经是修复版,不要只看 shell 里的 php -v。然后用 ps -eo pid,lstart,args | grep -i '[p]hp' 确认 PHP 进程的启动时间晚于升级时间。宝塔可以同时安装多个 PHP 版本,不同站点可能用不同版本,每个都要检查。
设置了 session.upload_progress.enabled=0,算不算修复了?
不算。PHP 公告说这个设置只能挡住 CVE-2026-109619 已确认的 session 路径,挡不住其他变种,而且对 CVE-2026-109618 没有作用。它必须写在 php.ini 或 FPM 池配置里,并重启每一个 worker 才生效。完整的修复只有更新 PHP 一条路。
我还在用 PHP 7.4 或 8.1,怎么办?
PHP 项目已经不再更新 8.1 及更早的版本。Ubuntu 22.04 的 php8.1 有官方修复版 8.1.2-1ubuntu2.28,20.04 的 php7.4 要通过 Ubuntu Pro 获取。宝塔为 Linux 面板打包了 7.4.34 和 8.1.35。Docker 官方镜像没有 7.4 和 8.1 的修复版标签。如果你的来源没有给出修复版,就把站点迁到 8.3 或更新的版本,因为 8.2 在 2026 年 12 月 31 日也会结束支持。
更新了 WordPress 或 Laravel,是不是就安全了?
不是。WordPress、Laravel 和 Composer 更新的只是用 PHP 写的应用代码,PHP 解释器本身没有任何变化。PHP 公告指出,针对 mod_php 的攻击可以在任何应用代码运行之前触发。你需要更新的是 PHP 运行环境本身,然后重启 PHP-FPM、Apache 和所有长时间运行的 PHP 进程。