版本 v1.0|信息截至 2026-10-22(北京时间)|适用对象:自己管服务器、或用虚拟主机/托管 WordPress 的独立开发者、小团队、接外包的个人站长。
这份文档只做“自查与修复”,不包含、也不链接任何漏洞利用代码。漏洞信息每天都在变,动手前请先看第 7 节列出的官方来源有没有新的更新。
0. 先看这一段:30 分钟行动清单
如果你只有半小时,按顺序做这五件事:
- 查清你所有站点实际在跑的 PHP 版本(不是服务器默认版本,是每个站点、每个容器各自的版本)。方法见第 3 节。
- 对照修复版本:8.5 系列要 ≥ 8.5.12,8.4 ≥ 8.4.27,8.3 ≥ 8.3.36,8.2 ≥ 8.2.35。8.1 及更老的版本没有官方发布版,等同“未修复”。
- 能升就立刻升,升完重启所有 PHP 进程(PHP-FPM / Apache / 容器),再复查一次版本。
- 升不了的站,先下线或挡到 WAF 后面(Cloudflare 免费版已有针对性规则),并联系主机商。
- 从 10 月 17 日开始倒查日志和文件,看有没有被打过。漏洞公开是 21 日,但已有人观测到 17 日就出现了攻击尝试。方法见第 5 节。
一句话原则:更新 WordPress、Laravel、插件都不会更新 PHP 本身。 这次的洞在 PHP 运行时里,必须升级 PHP。
1. 发生了什么
2026 年 10 月 20 日,两个 PHP 核心漏洞的编号 CVE-2026-109618 和 CVE-2026-109619 在 Hacker News 和社交媒体上提前泄露,被称为 PHP2Shell(php2shell)。PHP 安全团队原计划 22 日发布修复,因泄露提前到 21 日,发布了 8.5.12、8.4.27、8.3.36、8.2.35 四个修复版本。两个漏洞的官方评分都是 CVSS v4.0 9.2(严重),都可以让未登录的远程攻击者在服务器上执行任意代码citation。
两个洞的原理用大白话说:
| 编号 | 俗称 | 发生了什么 |
|---|---|---|
| CVE-2026-109618 | OOM 内存损坏 | PHP 用 memory_limit 限制内存,超限时会强行中断请求。如果中断发生在请求数据只初始化了一半的时候(比如正在解析表单文件上传),清理阶段会把这些“半成品”当成正常数据去释放,而这块内存里可能还残留着攻击者之前塞进去的数据。 |
| CVE-2026-109619 | 残留全局状态(释放后使用) | 某个请求的内部状态在内存已释放后还“活着”(比如发生致命错误之后),同一个 worker 处理的下一个请求可能在重置之前就用上了这块脏数据。官方还确认它能造成内存信息泄露。 |
来源:citation
为什么这次格外危险:
- 影响面极广。 从 PHP 5.4 到 8.5.11 都受影响;PHP-FPM、Apache mod_php、php-cgi、FrankenPHP、LiteSpeed 所有对外提供网页的部署方式都在范围内。PHP 官方认为 mod_php 风险最高,因为演示的攻击在任何应用代码运行之前就能触发citation。
- 已经被实际利用。 Amazon 威胁情报称 10 月 17 日(公开前 4 天)就在蜜罐上看到了攻击尝试,现在已是“大范围利用”;Cloudflare 和 watchTowr 也观测到攻击citation。
- 老版本没人管。 10 月 20 日的统计显示,在 HTTP 头里暴露 PHP 版本的约 150 万台主机中,约 88.4 万台(59%)还是 8.1 或更老的版本citation。研究者称已在实验室对 WordPress、Drupal、Joomla、Magento 的特定版本实现了免登录代码执行。
另外提醒:网上流传的“PoC”很多未经验证,有些可能夹带恶意程序。不要下载、不要在自己服务器上跑来路不明的利用代码“试试看”。
2. 我受不受影响?按你的部署方式对号入座
| 你的情况 | 谁负责升级 PHP | 你要做的事 |
|---|---|---|
| 虚拟主机 / cPanel / Plesk / 宝塔面板 | 主机商提供 PHP 包,但版本常由你在面板里选 | 每个站点单独检查(cPanel 看 MultiPHP Manager,Plesk 看每个订阅的 Hosting Settings),切到已修复的版本;没有修复版就发工单催 |
| 托管 WordPress(各类 Managed WP) | 主机商 | 看主机商公告是否已推送修复;后台“站点健康”里能看到 PHP 版本 |
| 自己的 VPS / 云服务器(apt / yum 装的 PHP) | 你自己 | 用发行版的包更新;注意发行版常“打补丁不改主版本号”,要看更新日志(见 3.3) |
| Docker / 容器部署 | 你自己(重建镜像) | 拉取新基础镜像并重建、重新部署,只重启旧容器没用。官方 php 和 wordpress 镜像已带上修复版citation |
| PaaS / Serverless(各类托管运行时) | 平台方 | 查平台安全公告,确认运行时已更新;必要时触发一次重新部署 |
| 用了 remi 源(rpms.remirepo.net) | 你自己 | 该志愿者源 21 日当天没有更新,建议切回发行版官方包或自行编译citation |
| PHP 7.4 / 8.0 / 8.1 | 没有官方发布版 | PHP 团队只提供了最小源码补丁给能自己编译的人;其余情况请直接升级到 8.2+,或找发行版 / LTS 供应商 |
| PHP 5.4 – 7.3 | 没有任何修复 | 只能升级。这类站点今天就应该先下线 |
还有一类容易漏掉的:你没意识到自己在跑 PHP 的东西——NAS 管理界面、路由器/面板、某些商业软件后台、很多年前给客户搭的小站。荷兰 NCSC 在 GitHub 维护了一份“内嵌 PHP 的第三方软件”清单可以对照citation。
注意 8.2 的寿命:8.2 系列只在 2026 年 12 月 31 日前提供安全修复citation。这次既然要动,建议直接升到 8.3 或 8.4,省得两个月后再折腾一次。
3. 怎么查版本(三层,从外到内)
3.1 从外面看(不需要登录服务器)
curl -sI https://你的域名 | grep -i x-powered-by
如果返回 X-Powered-By: PHP/8.3.12 之类,就能看到对外暴露的版本。但这只是参考:很多站关掉了这个头,CDN 也可能把它去掉;而且发行版打过补丁的包,版本号可能看起来“旧”其实已修复。外部检测只能证明“有问题”,不能证明“没问题”。
3.2 在服务器上看(最准确)
php -v # 命令行版本
php-fpm -v # 网页实际使用的 FPM 版本(名字可能是 php-fpm8.3、php83-php-fpm 等)
php-cgi -v # 如果用 CGI
命令行的 PHP 和网页用的 PHP 经常不是同一个。 务必查 FPM / Apache 模块那一个。实在找不到,可以临时放一个只有 <?php echo PHP_VERSION; 的文件到站点目录访问一下,看完马上删除(千万别留 phpinfo() 在线上)。
3.3 发行版“偷偷打补丁”的情况
Debian / Ubuntu / RHEL 系常把补丁打进旧版本号里,所以要看包的更新日志里有没有这次的 CVE 编号:
# Debian / Ubuntu(把包名换成你实际的,如 php8.2-fpm)
apt changelog php8.2-fpm 2>/dev/null | grep -m1 CVE-2026-109618
# RHEL / Rocky / Alma / CentOS Stream
rpm -q --changelog php-fpm | grep -m1 CVE-2026-109618
能搜到编号,说明这个包已包含修复;搜不到,说明还没更新或源里还没有。
4. 修复:怎么升、升完怎么验收
唯一完整的修复是升级 PHP。 PHP 核心开发者原话是“唯一的缓解措施就是打补丁 / 升级 PHP”citation。
常见环境的做法(示例,按你的实际包名调整):
# Debian / Ubuntu
sudo apt update && sudo apt install --only-upgrade 'php8.*'
sudo systemctl restart php8.3-fpm # 以及 apache2 / nginx
# RHEL 系
sudo dnf update 'php*'
sudo systemctl restart php-fpm httpd
# Docker:改 Dockerfile 的基础镜像标签到修复版本,然后
docker compose build --pull && docker compose up -d
验收标准(全部满足才算修好):
- 网页实际使用的 PHP(FPM / 模块)版本 ≥ 对应修复版本,或包更新日志里能查到 CVE-2026-109618 / 109619;
- 所有 PHP worker 已重启(
systemctl status看启动时间晚于升级时间;容器看创建时间); - 站点能正常打开、登录、上传文件(升级可能带来兼容问题,顺手测一下关键流程);
- 同一台机上每个站点都查过,不只是默认站点。
升不了的时候:临时措施(不等于修复)
PHP 官方列了几个能降低特定攻击路径风险的配置,但明确说做了也不代表安全citation:
- 针对 109618:把
memory_limit设为-1,并且让应用无法覆盖(在 php.ini / FPM 池配置里用php_admin_value);同时用容器或 cgroup 内存上限兜底,防止服务器被拖垮。 - 针对 109619:
session.upload_progress.enabled=0;PHP 8.5 还要fatal_error_backtraces=0。 - 改完必须重启所有 PHP worker。
- 把站点挡到 Cloudflare 后面:Cloudflare 已为包括免费版在内的所有用户部署了针对已知攻击方式的 WAF 规则,但它自己也说这只是权宜之计,而且只保护经过 Cloudflare 的流量(源站 IP 暴露的话等于没挡)。
- 实在不行,先下线。CERT-EU 的建议就是把无法修复的公网应用先隔离citation。
关于
session.upload_progress有争议:早期很多文章把它当“根源”,但这一说法在补丁发布前并未得到 PHP 官方确认citation。按官方公告来,把它当“减少一条路径”,不要当“盾牌”。
5. 我是不是已经被打了?(从 10 月 17 日开始查)
Amazon 建议调查起点至少放在 10 月 17 日,而不是公开日citation。重要提醒:如果怀疑已被入侵,先保留证据(日志、可疑文件拷贝)再升级,否则重启后很多线索就没了。
5.1 看日志里的异常信号
这两个洞都和内存耗尽、worker 崩溃有关,所以重点找这几类记录:
grep -hE "Allowed memory size|exited on signal (11|6)|SIGSEGV|SIGABRT" \
/var/log/php*fpm*.log /var/log/apache2/error.log /var/log/httpd/error_log \
/var/log/nginx/error.log 2>/dev/null | tail -n 100
journalctl --since 2026-10-17 2>/dev/null | grep -E "php.*(SIGSEGV|signal 11|Allowed memory size)" | tail -n 50
10 月 17 日之后,如果突然出现一批平时没有的内存耗尽或段错误,尤其集中在对上传接口、首页的 POST 请求上,就要警惕。
5.2 看有没有多出来的 PHP 文件(网页后门)
# 10 月 17 日之后新增或被改过的 PHP 文件
find /var/www -type f -name '*.php' -newermt 2026-10-17 2>/dev/null | head -n 200
# 上传目录里本不该有 PHP 文件
find /var/www -type f -path '*upload*' -name '*.php*' 2>/dev/null
# 常见后门特征(会有误报,逐个人工看)
grep -rlE "eval\(base64_decode|gzinflate\(base64_decode|assert\(\\\$_(POST|GET|REQUEST)" \
/var/www --include='*.php' 2>/dev/null | head -n 50
5.3 看持久化手段
- 定时任务:
crontab -l、sudo crontab -u www-data -l(或 apache / nginx 用户)、ls -la /etc/cron.d /etc/cron.hourly - CMS 后台有没有多出管理员账号(WordPress 看“用户”列表,按注册时间排序)
- 服务器上有没有陌生进程、陌生的 SSH 公钥(
~/.ssh/authorized_keys)
5.4 发现可疑怎么办
一旦确认被入侵,“删掉后门文件”远远不够。按这个顺序:保留证据 → 站点下线 → 从干净的备份或镜像重建 → 升级 PHP → 轮换所有密钥(数据库密码、.env 里的 API Key、云服务 AccessKey、支付密钥、WordPress 的 salts)→ 重新上线后持续观察日志。同一台机器上的其他站点也要视为可能被波及。
6. 借这次机会做一遍的基础加固
这次的洞再严重,也只是 PHP 安全问题里的一个。下面这些平时就该做,顺手一起做掉:
运行时层面
- 关掉版本暴露:php.ini 里
expose_php = Off,让扫描器没那么容易盯上你。 - 不再用任何 EOL 版本(目前 8.1 及以下);记下 8.2 会在 2026 年底停止安全支持。
- 上传目录禁止执行 PHP(Nginx 在 uploads 路径
location里拒绝.php;Apache 用.htaccess或配置禁用)。 - 生产环境
display_errors = Off,错误只写日志。 - 一台机器跑多个客户站时,每个站用独立的 FPM 池和系统用户,一个站被打穿不至于全军覆没。
应用层面
- 有
composer.lock的项目跑一下composer audit(Composer 2.4+ 自带),它会对照公开漏洞库列出有问题的依赖。 - WordPress:删掉不用的插件和主题(停用≠安全,文件还在就可能被利用),剩下的保持更新;注意 2026 年 7 月 WordPress 自身也出过一对“wp2shell”漏洞(CVE-2026-63030、CVE-2026-60137),和这次 PHP2Shell 是两回事,各修各的citation。
.env、.git、备份压缩包、phpinfo.php不要放在网站可访问的目录里。可以用curl -sI https://你的域名/.env检查,应返回 403/404。
习惯层面
- 给自己建一张“我在跑的东西”清单:域名、服务器、PHP 版本、部署方式、谁负责升级。这次最耽误时间的往往不是升级本身,而是想不起来自己到底有几个站。
- 订阅下面第 7 节的一两个来源,严重漏洞出来时能第一时间知道。
7. 去哪里看实时更新
漏洞信息变化很快,这份文档只是 10 月 22 日的快照。以下来源按可信度排序:
| 来源 | 看什么 |
|---|---|
| php.net 首页新闻 / PHP 安全公告(GitHub) | 一手信息:修复版本、官方认可的缓解措施 |
| 你的发行版安全公告(Debian DSA、Ubuntu USN、Red Hat RHSA) | 你的包什么时候修好、修在哪个包版本 |
| CERT-EU、加拿大网络安全中心、CISA、英国 NCSC | 政府级预警,信息克制、准确 |
| CISA KEV(已被利用漏洞目录) | 某个漏洞是否已确认被在野利用。PHP 历史上进过 KEV 的有 CVE-2024-4577(PHP-CGI)、CVE-2019-11043(PHP-FPM)等,两者都被勒索软件用过 |
| Tenable FAQ、Unit 42、watchTowr、Wiz 等安全厂商博客 | 利用情况、攻击时间线,更新最勤 |
| NCSC-NL php2shell 仓库 | 哪些第三方软件内嵌了受影响的 PHP |
附录 A:一键只读自查脚本
下面这个脚本只读不改:查版本、查补丁、查关键配置、扫日志和可疑文件。把输出整段贴给配套的「PHP 安全自查助手」Skill,它会帮你逐项判断并给出修复步骤。
说明:脚本基于常见的 Debian/Ubuntu/RHEL 路径编写,尚未在所有环境中实测;个别命令在你的系统上不存在时会静默跳过。请用有 sudo 权限的账号运行,以便读取日志。网站目录不是
/var/www的,改第一行的WEBROOT。
#!/usr/bin/env bash
# php-selfcheck.sh — PHP 安全只读自查(PHP2Shell 版,2026-10)
WEBROOT="${WEBROOT:-/var/www}"
SINCE="2026-10-17"
echo "===== 0. 系统 ====="
uname -a; cat /etc/os-release 2>/dev/null | head -n 3
echo; echo "===== 1. 本机所有 PHP 可执行文件及版本 ====="
find /usr/bin /usr/sbin /usr/local/bin /usr/local/sbin /opt -maxdepth 6 \
\( -name 'php' -o -name 'php[0-9]*' -o -name 'php-fpm*' -o -name 'php-cgi*' \) 2>/dev/null \
| sort -u | while read -r b; do
[ -x "$b" ] && [ ! -d "$b" ] && echo "$b -> $("$b" -v 2>/dev/null | head -n1)"
done
echo; echo "===== 2. 正在运行的 PHP 进程 ====="
ps -eo pid,lstart,cmd | grep -E "php|httpd|apache2|frankenphp|lsphp" | grep -v grep | head -n 20
echo; echo "===== 3. 发行版包与补丁日志 ====="
if command -v dpkg >/dev/null; then
dpkg -l | awk '/^ii/ && ($2 ~ /^php[0-9.]*-(fpm|cli|cgi)$/ || $2 ~ /libapache2-mod-php/) {print $2, $3}' | while read -r p v; do
hit=$(apt changelog "$p" 2>/dev/null | grep -m1 -oE "CVE-2026-10961[89]")
echo "$p $v 补丁记录: ${hit:-未找到}"
done
fi
if command -v rpm >/dev/null; then
rpm -qa | grep -E '^php' | sort | while read -r p; do
hit=$(rpm -q --changelog "$p" 2>/dev/null | grep -m1 -oE "CVE-2026-10961[89]")
echo "$p 补丁记录: ${hit:-未找到}"
done
fi
echo; echo "===== 4. 关键配置(命令行 PHP 视角,FPM 可能不同)====="
php -r 'foreach(["memory_limit","post_max_size","session.upload_progress.enabled","fatal_error_backtraces","expose_php","display_errors"] as $k){echo $k,"=",var_export(ini_get($k),true),PHP_EOL;}' 2>/dev/null
grep -rhE "^\s*php_(admin_)?(value|flag)\[(memory_limit|session.upload_progress.enabled)\]" /etc/php* /etc/opt 2>/dev/null | head
echo; echo "===== 5. Docker 容器内 PHP 版本 ====="
if command -v docker >/dev/null; then
docker ps --format '{{.Names}} {{.Image}} {{.CreatedAt}}' 2>/dev/null | while read -r n rest; do
echo "$n ($rest) -> $(docker exec "$n" php -v 2>/dev/null | head -n1)"
done
fi
echo; echo "===== 6. 日志异常($SINCE 起)====="
grep -hE "Allowed memory size|exited on signal (11|6)|SIGSEGV|SIGABRT" \
/var/log/php*fpm*.log /var/log/php*/*.log /var/log/apache2/error.log* /var/log/httpd/error_log* \
/var/log/nginx/error.log* 2>/dev/null | tail -n 60
journalctl --since "$SINCE" 2>/dev/null | grep -E "php.*(SIGSEGV|signal 11|signal 6|Allowed memory size)" | tail -n 30
echo; echo "===== 7. 可疑文件 ====="
echo "-- $SINCE 后新增/修改的 PHP 文件(前 100 个)"
find "$WEBROOT" -type f -name '*.php' -newermt "$SINCE" 2>/dev/null | head -n 100
echo "-- 上传目录中的 PHP 文件"
find "$WEBROOT" -type f -path '*upload*' -name '*.php*' 2>/dev/null | head -n 50
echo "-- 后门特征命中(需人工复核)"
grep -rlE 'eval\(base64_decode|gzinflate\(base64_decode|assert\(\$_(POST|GET|REQUEST)' \
"$WEBROOT" --include='*.php' 2>/dev/null | head -n 50
echo; echo "===== 8. 定时任务 ====="
crontab -l 2>/dev/null
for u in www-data apache nginx nobody; do echo "-- $u"; crontab -u "$u" -l 2>/dev/null; done
ls -la /etc/cron.d 2>/dev/null
echo; echo "===== 9. 敏感文件暴露(本地检查是否在网站目录)====="
find "$WEBROOT" -maxdepth 4 \( -name '.env' -o -name '.git' -o -name 'phpinfo.php' -o -name '*.sql' -o -name '*.zip' -o -name '*.tar.gz' \) 2>/dev/null | head -n 50
echo; echo "===== 完成。请把以上全部输出贴给「PHP 安全自查助手」。注意先删掉里面的密码等敏感信息。====="
附录 B:打印版检查清单
- 列出所有站点 / 服务器 / 容器,以及各自的 PHP 版本和负责人
- 每个对外站点的 PHP 版本 ≥ 8.5.12 / 8.4.27 / 8.3.36 / 8.2.35,或包日志可查到 CVE 编号
- 8.1 及以下的站点已升级或已下线
- 升级后所有 PHP worker 和容器已重启 / 重建
- 升不了的站已挡在 WAF 后,且源站 IP 不对外
- 已从 10 月 17 日起检查日志中的内存耗尽 / 段错误
- 已检查新增 PHP 文件、上传目录 PHP 文件、可疑定时任务、陌生管理员账号
- 如有入侵迹象:已保留证据、重建、轮换全部密钥
-
expose_php = Off、display_errors = Off、上传目录禁止执行 PHP - 跑过
composer audit;WordPress 已清理无用插件主题 - 已订阅至少一个官方安全公告来源