记一次 PHP 重大 RCE 漏洞应急响应与 Nextcloud 升级实践
2026 年 10 月,PHP 官方紧急披露了两个可导致未认证远程代码执行(RCE)的严重漏洞。本文记录了笔者在一台运行 Nextcloud 的生产服务器上,从漏洞发现、风险评估,到 PHP 升级、Nextcloud 跨大版本迁移,再到数据落盘的完整过程。
背景:两个「9.2 分」的致命漏洞¶
漏洞披露¶
2026 年 10 月 21 日,PHP 官方发布紧急安全公告,披露了两个 CVSS v4.0 评分 9.2(Critical)的远程代码执行漏洞:
| CVE 编号 | 漏洞类型 | 危害 |
|---|---|---|
| CVE-2026-109618 | Use-After-Free(OOM 处理内存未完全初始化) | 未认证 RCE |
| CVE-2026-109619 | Use-After-Free(陈旧全局状态) | 未认证 RCE |
这两个漏洞的共同特点是:攻击者无需任何身份认证,只需构造恶意 HTTP 请求即可在受影响服务器上执行任意代码。更严峻的是,多个国家级安全机构(CNVD、JPCERT、TWCERT、GovCERT.HK)几乎同时发出警报,并确认漏洞 PoC 已公开且被野外积极利用。
风险等级
两个漏洞均为 Critical(严重) 级别,CVSS v4.0 评分 9.2 分,且已被野外利用。所有通过 Web 提供服务的 PHP 部署均受影响。
受影响范围¶
几乎所有通过 Web 提供服务的 PHP 部署方式均受影响:
- PHP-FPM(FastCGI)
- Apache + mod_php
- php-cgi
- FrankenPHP、LiteSpeed 等
官方修复版本为:
| 分支 | 修复版本 |
|---|---|
| PHP 8.2 | 8.2.35 |
| PHP 8.3 | 8.3.36 |
| PHP 8.4 | 8.4.27 |
| PHP 8.5 | 8.5.12 |
本文服务器的现状¶
| 项目 | 版本 | 风险 |
|---|---|---|
| 操作系统 | Ubuntu 22.04.2 LTS | - |
| PHP | 8.1.2(EOL,官方已停止支持) | 🔴 高危 |
| Nextcloud | 26.0.0(2023 年 3 月发布) | 🔴 严重落后 |
| Web 服务器 | nginx 1.18.0 | - |
| 数据库 | MySQL 8.0.46 | - |
关键问题
PHP 8.1 已经 EOL(End of Life),官方明确表示「不再为 8.1 发布修复版本」。虽然 Ubuntu 作为 LTS 发行版会提供 backport 补丁,但多个权威机构仍建议升级到受支持的 PHP 版本以彻底消除风险。
应急响应:立即停服¶
在确认漏洞的严重性后,第一步是立即停止 PHP 服务,阻断潜在的攻击面:
这一步虽然会导致 Nextcloud 暂时不可用,但在「可用性」与「安全性」之间,面对正在被野外利用的 RCE 漏洞,安全永远优先。
备份:升级前的安全网¶
在动任何东西之前,完整的备份是必须的。本次备份了三类数据:
# 1. 备份数据库
mysqldump -u root -p'***' nextcloud > /data/nextcloud-backup/nextcloud-db-$(date +%F).sql
# 2. 备份配置文件
cp /var/www/nextcloud/config/config.php /data/nextcloud-backup/config.php.bak
# 3. 备份数据目录(用户文件,1.7G,压缩后 1.4G)
tar czf /data/nextcloud-backup/nextcloud-data-$(date +%F).tar.gz \
-C /var/www/nextcloud data
经验
备份一定要放在不同的物理磁盘上。本次系统盘只剩 2.7G,而数据目录就有 1.7G,得益于外挂了一块 40G 的数据盘 /data,备份才得以安全存放。
PHP 升级:8.1 → 8.5¶
为什么要升级到 8.5 而非 8.2¶
虽然官方修复版本覆盖 8.2~8.5,但考虑到:
- PHP 8.5 是最新的稳定版,获得最长的官方支持周期
- Nextcloud 35 明确推荐 PHP 8.5
- 避免「刚升级完又接近 EOL」的尴尬
因此目标定为 PHP 8.5.12。
Ubuntu 官方源没有 PHP 8.5¶
Ubuntu 22.04 官方源只提供 PHP 8.1,要安装 8.5 必须引入第三方 PPA。这里采用了社区广泛使用的 ondrej/php PPA:
# 添加 PPA
add-apt-repository -y ppa:ondrej/php
apt-get update
# 安装 PHP 8.5 及 Nextcloud 所需扩展
apt-get install -y \
php8.5-fpm php8.5-cli php8.5-common \
php8.5-mysql php8.5-gd php8.5-xml php8.5-curl \
php8.5-mbstring php8.5-zip php8.5-intl \
php8.5-bz2 php8.5-bcmath php8.5-gmp \
php8.5-apcu php8.5-imagick php8.5-redis \
php8.5-ldap php8.5-smbclient php8.5-xmlreader
注意
PHP 8.5 中 opcache 和 fileinfo 已内置,无需单独安装。
切换 nginx 到 PHP 8.5¶
修改 nginx 站点配置中的 FastCGI 转发目标:
# 修改 /etc/nginx/sites-available/cloud.example.com
fastcgi_pass unix:/run/php/php8.5-fpm.sock; # 原为 php8.1-fpm.sock
然后重启服务:
systemctl start php8.5-fpm
systemctl stop php8.1-fpm
systemctl disable php8.1-fpm
systemctl restart nginx
Nextcloud 升级:26 → 35(跨 9 个大版本)¶
升级策略:重新安装 vs 逐版本升级¶
面对 Nextcloud 26 → 35 的巨大跨度,有两条路:
- 逐版本升级:26→27→28→...→35,每个大版本跑一次
occ upgrade - 重新安装:下载最新版代码,指向原数据库和数据目录
最初尝试「重新安装」——但执行 occ upgrade 时,Nextcloud 直接拒绝:
结论
Nextcloud 强制要求逐版本升级,不能跨多个大版本。 因此只能采用策略 1。
升级路径规划¶
Nextcloud 官方规则:每次最多升一个大版本,且必须升到该大版本的最新补丁版。
规划的完整路径:
关键坑点:PHP 版本兼容性¶
这是本次升级中最隐蔽的坑。不同版本的 Nextcloud 对 PHP 版本有硬性限制:
| Nextcloud 版本 | 支持的 PHP 版本上限 |
|---|---|
| 27 | < 8.3 |
| 28 | 8.0 - 8.3 |
| 29 | 8.1 - 8.3 |
| 30 | 8.1 - 8.4 |
| 31 | 8.1 - 8.4(不支持 8.5) |
| 32 | 8.1 - 8.4(不支持 8.5) |
| 33 | 8.2 - 8.5 ✅ |
| 34 | 8.2 - 8.5 ✅ |
| 35 | 8.3 - 8.5 ✅ |
矛盾点
我们已升级到 PHP 8.5,但 Nextcloud 27~32 的 occ 命令在 PHP 8.5 下会直接报错:
解决方案:同时安装 PHP 8.2(仅 CLI,无需 fpm),升级中间版本时用 php8.2 执行 occ:
# 升级 27~32 时
sudo -u www-data php8.2 occ upgrade
# 升级 33~35 时(已支持 8.5)
sudo -u www-data php8.5 occ upgrade
这保证了网站全程使用 PHP 8.5 的 fpm,而 CLI 升级过程根据版本灵活切换。
自动化升级脚本¶
为了减少重复操作,编写了自动化脚本。完整脚本如下,请根据实际情况修改版本号和 PHP 路径:
#!/bin/bash
# Nextcloud 逐版本升级脚本:26 -> 35(自动切换 PHP 版本)
# 27-32 用 PHP 8.2,33-35 用 PHP 8.5
# 用法:bash upgrade-nextcloud.sh
set -euo pipefail
NC_DIR="/var/www/nextcloud"
BACKUP_DIR="/data/nextcloud-backup"
DOWNLOAD_BASE="https://download.nextcloud.com/server/releases"
# 升级路径:版本 及 对应的 PHP 二进制
# 注意:27-32 不支持 PHP 8.5,需用 8.2
UPGRADES=(
"27.1.11|php8.2"
"28.0.14|php8.2"
"29.0.16|php8.2"
"30.0.17|php8.2"
"31.0.14|php8.2"
"32.0.15|php8.2"
"33.0.10|php8.5"
"34.0.5|php8.5"
"35.0.2|php8.5"
)
echo "=============================================="
echo "Nextcloud 逐版本升级:26 -> 35"
echo "=============================================="
for entry in "${UPGRADES[@]}"; do
ver="${entry%%|*}"
phpbin="${entry##*|}"
echo ""
echo "########## 升级到 Nextcloud $ver (使用 $phpbin) ##########"
# 下载
ZIP="/data/nextcloud-backup/nextcloud-${ver}.zip"
if [ ! -f "$ZIP" ]; then
echo "[$ver] 下载..."
curl -sL --max-time 300 "${DOWNLOAD_BASE}/nextcloud-${ver}.zip" -o "$ZIP"
echo "[$ver] 下载完成: $(du -h "$ZIP" | cut -f1)"
else
echo "[$ver] 已存在,跳过下载"
fi
# 解压到临时目录
TMP="/tmp/nc-upgrade-${ver}"
rm -rf "$TMP"
mkdir -p "$TMP"
echo "[$ver] 解压..."
unzip -q "$ZIP" -d "$TMP"
# 临时移出 config 和 data(这是最干净的方式,避免残留)
echo "[$ver] 移出 config 和 data..."
mv "$NC_DIR/config" "/tmp/nc-config-${ver}"
mv "$NC_DIR/data" "/tmp/nc-data-${ver}"
# 删除旧代码,放入新代码
echo "[$ver] 替换代码目录..."
rm -rf "$NC_DIR"
mv "$TMP/nextcloud" "$NC_DIR"
# 恢复 config 和 data
echo "[$ver] 恢复 config 和 data..."
rm -rf "$NC_DIR/config"
mv "/tmp/nc-config-${ver}" "$NC_DIR/config"
mv "/tmp/nc-data-${ver}" "$NC_DIR/data"
# 权限
chown -R www-data:www-data "$NC_DIR"
# 确认版本
VER_IN_CODE=$(grep -oP "OC_VersionString = '\K[^']+" "$NC_DIR/version.php")
echo "[$ver] 代码版本:$VER_IN_CODE(PHP: $($phpbin -v | head -1 | grep -oP 'PHP \K[0-9.]+'))"
# 执行升级
echo "[$ver] 执行 occ upgrade..."
cd "$NC_DIR"
sudo -u www-data $phpbin occ maintenance:mode --off 2>&1 | tail -2 || true
sudo -u www-data $phpbin occ upgrade --no-interaction 2>&1 | tail -30 || {
echo "[$ver] ❌ 升级失败!"
echo "config.php 和 data 已保留在 $NC_DIR"
echo "请检查日志后重试。"
exit 1
}
echo "[$ver] ✅ 升级成功"
rm -rf "$TMP"
done
echo ""
echo "=============================================="
echo "全部升级完成!当前版本:"
cd "$NC_DIR"
sudo -u www-data php8.5 occ status 2>&1 | grep -E "version|versionstring"
echo "=============================================="
脚本要点
- 先备份:脚本假设你已经完成了
config/和data/的备份 - 移出而非覆盖:通过
mv临时移出config/和data/,再整目录替换代码,避免rsync --delete导致的残留文件问题 - 自动切换 PHP:
UPGRADES数组里每项标注了对应的 PHP 二进制,脚本自动选择
升级后的 schema 修复¶
升级到 35 后,occ db:schema:check 报告一个差异:
这是因为 Nextcloud 33 引入了一个迁移,要移除 share_external.id 的 auto-increment 属性,但跨版本升级时该迁移未正确执行。手动修复:
修复后再次检查:
遗留问题:.mjs 文件 404¶
升级后浏览器控制台大量报 404 错误:
根因:Nextcloud 新版使用 ES modules(.mjs 文件),但旧 nginx 配置不识别 .mjs 扩展名,导致这些文件被错误地重写到 index.php。
修复方法:
# 1. /etc/nginx/mime.types 添加 .mjs
application/javascript js mjs;
# 2. 站点配置的 location 规则补充 .mjs 和 .wasm
location ~* \.(?:css|js|mjs|wasm)$ {
try_files $uri /index.php$uri$is_args$args;
add_header Cache-Control "public, max-age=7200";
add_header X-Content-Type-Options nosniff;
# ... 其它安全头 ...
access_log off;
}
重载后验证:
数据迁移:外挂硬盘¶
升级完成后,发现系统盘(24G)已用 91%,而外挂数据盘(40G)仅用 27%。于是将 Nextcloud 数据目录迁移到外挂盘:
# 1. 进入维护模式
sudo -u www-data php8.5 occ maintenance:mode --on
# 2. rsync 迁移(保留权限和时间戳)
rsync -a /var/www/nextcloud/data/ /data/nextcloud-data/
# 3. 验证文件数一致
find /var/www/nextcloud/data -type f | wc -l # 源
find /data/nextcloud-data -type f | wc -l # 目标(应一致)
# 4. 设置权限
chown -R www-data:www-data /data/nextcloud-data
# 5. 修改 config.php
# 'datadirectory' => '/data/nextcloud-data'
# 6. 退出维护模式并验证
sudo -u www-data php8.5 occ maintenance:mode --off
sudo -u www-data php8.5 occ files:scan --all
# 7. 确认无误后删除旧目录
rm -rf /var/www/nextcloud/data
迁移结果
数据目录从系统盘迁移到外挂盘后,系统盘占用从 91% 降至 84%,释放了 1.7G 空间。用户文件(1.7G、8577 个文件)完整无损。
最终结果¶
| 项目 | 升级前 | 升级后 |
|---|---|---|
| PHP | 8.1.2(EOL,有 RCE 漏洞) | 8.5.12(最新安全版) |
| Nextcloud | 26.0.0 | 35.0.2(2026 最新) |
| 数据完整性 | - | ✅ 128 张表、1.7G 用户文件完整迁移 |
| 系统盘占用 | 88% | 71%(释放 3.5G+) |
经验总结¶
教训¶
-
EOL 版本是最大的风险:PHP 8.1 已 EOL,面对重大漏洞时「无路可退」,只能硬升级。生产环境的运行时和框架应保持在受支持版本内。
-
跨大版本升级没有捷径:Nextcloud 强制逐版本升级,且中间版本对 PHP 版本有硬限制,必须提前规划好「版本 × PHP」的兼容矩阵。
-
备份要放在不同磁盘:本次系统盘空间紧张,幸好在另一块盘上有备份空间,否则备份都成问题。
做得好的地方¶
- 第一时间停服:面对正在被野外利用的 RCE,果断停止服务是正确的。
- 完整备份先行:数据库 + 配置 + 数据目录三份备份,让后续所有操作有了安全网。
- 自动化脚本:9 个版本的升级用脚本批量处理,减少人为失误。
给读者的建议¶
行动清单
- ⚠️ 如果你还在用 PHP 8.1 或更早版本,立即检查并规划升级到 8.2.35+ / 8.3.36+ / 8.4.27+ / 8.5.12+
- Nextcloud 用户请关注官方系统要求,规划好升级路径
- 重要数据定期备份,且备份不要与源数据在同一块磁盘
- 升级前务必在测试环境验证完整流程
本文涉及的所有配置和命令均在 Ubuntu 22.04 + nginx + PHP-FPM + MySQL 环境下验证通过。
升级操作请务必先在测试环境验证,并在生产环境做好完整备份后再执行。