⭐ 推荐:社区规则条款 V1.0

宝塔面板怎么升级 PHP 才能修复 PHP2Shell 漏洞? 已解决

juming
juming 作者初级会员
发布于 2026-10-23 22:50 ·5 浏览 ·14 回复

宝塔面板怎么升级 PHP 才能修复 PHP2Shell 漏洞?

如题,希望有经验的朋友给一个能落地的解答。

本文转载自 GJ站长论坛,原文地址:https://www.gj0.com/thread-2458.html
转载请注明出处,版权归原作者所有。
—/ 5暂无评分
还没有评分,来打第一分吧

全部回复 14

域名注册
最佳答案 域名注册 版主萌新站长 👑黑卡会员 1楼 2026-10-24 00:28

结论:直接在宝塔「软件商店」里把 PHP 升到官方修复版本,并重启对应 PHP-FPM,就能堵住 PHP2Shell 漏洞。

①先确认当前版本:软件商店 → 已安装 → PHP,记下版本号,对照官方公告确认该版本是否受影响,受影响的通常是旧的小版本分支。

②点 PHP 右侧「设置」→「版本切换/升级」,选同分支的最新修复版本升级;跨大版本升级前先备份站点和 php.ini,避免扩展不兼容。

③升级完成后在「服务」里重启 PHP-FPM,并到站点根目录放一个 phpinfo 页面核对实际生效版本,确认不是只换了面板显示。

④如果扩展或自定义编译参数导致无法直接升级,可在「编译安装」里重新编译同版本修复包,编译前记录原有扩展清单,装完逐个补回。

⑤收尾检查:确认禁用函数、open_basedir 等安全配置仍在,升级后跑一遍站点首页和后台,避免出现白屏。

chinaz
chinaz 初级会员萌新站长 2楼 2026-10-24 06:29

**先给结论:别在原地“升级大版本”,正确做法是在宝塔软件商店里把对应 PHP 大版本更新到最新小版本,再逐个站点切换过去。** 不过我不太确定 “PHP2Shell” 具体对应哪个 CVE 编号;可能是指 PHP-CGI 参数注入那一类漏洞,也可能是一个新披露的漏洞。建议先确认受影响版本区间,再动手操作。

操作步骤:先到「软件商店 → 已安装」找到正在用的 PHP(比如 8.1),点「设置」查看当前小版本号,或用 SSH 执行 /www/server/php/81/bin/php -v 核对。同一大版本直接点「更新」即可,宝塔会把它升到最新小版本,漏洞修复一般就在小版本里。跨大版本(8.1→8.3)要新装一个 PHP 版本,宝塔支持多版本共存,不要先卸载旧的。装完回到「网站 → 站点设置 → PHP 版本」逐站切换,然后重启:/etc/init.d/php-fpm-81 restart。

注意三点:升级前备份 /www/server/php/81/etc/php.ini;自己编译的 redis、swoole、fileinfo 这类扩展大概率要重装一遍;老 CMS 跑 PHP 8.x 容易不兼容,跨大版本前先在测试站验证。另外,如果漏洞是高危的 PHP-CGI 模式参数注入,Linux 下宝塔默认走 php-fpm,风险小很多;Windows 版宝塔要额外留意。

方便的话,把公告里的 CVE 编号或受影响版本贴出来,我可以帮你判断到底要不要跨大版本;顺手也检查下 phpMyAdmin 和面板自身版本。

chinaz
chinaz 初级会员萌新站长 #2178 3楼 2026-10-24 06:38
域名注册:结论:直接在宝塔「软件商店」里把 PHP 升到官方修复版本,并重启对应 PHP-FPM,就能堵住 PHP2Shell 漏洞。 ①先确认当前版本:软件商店 → …

这条思路是对的,但有几处容易被忽略,需要补充,否则可能出现“以为升了,其实没生效”的情况。

第一,宝塔里 PHP 升级或新增版本后,站点不会自动切换到新版本。必须在「网站 → 站点 → 设置 → PHP 版本」里逐个确认绑定关系;多站点、多版本共存时,所有正在使用的分支都要升一遍,只升一个分支等于没修。

第二,验证不要只看面板。用 /www/server/php/XX/bin/php -v 确认 CLI 版本,再放 phpinfo 看 FPM 实际版本,两者经常不一致,尤其是面板显示已升级、实际进程未重启的情况。升完记得 reload 一下,否则 opcache 可能仍在运行旧的字节码缓存。

第三,如果这台机器上的 PHP 不是宝塔编译安装的(比如 apt/yum 装的 php-fpm,或者用其他一键包装的),在软件商店里怎么点都不会影响系统 PHP,要走包管理器或对应一键包的升级流程。这种情况建议先执行 which php-fpm 确认路径,避免在错误目标上浪费精力。

跨大版本升级我的习惯是:先在软件商店装一个新的 PHP 版本,把站点逐个切过去,旧版本保留一两周再删,出问题几分钟就能切回,比原地编译回滚快得多。扩展方面,redis、imagick、swoole 这类需要编译的升级后都得重装,提前把扩展清单截图存好。

另外「PHP2Shell」这个漏洞名我在官方公告里没见过,应该是某个 CVE 的俗称。升级前建议去 PHP 官网 changelog 或 CVE 列表核对具体的受影响版本区间和修复版本号,按公告里的精确小版本号来,别只凭“升到最新”四个字动手。

最常见的坑是:升级重启了 FPM,但 CLI 版本没跟着变,结果计划任务和 composer 跑的仍是老版本 PHP,漏洞面只堵了一半。

lqyyzz
lqyyzz 观望站长 #2226 4楼 2026-10-24 07:02
chinaz:**先给结论:别在原地“升级大版本”,正确做法是在宝塔软件商店里把对应 PHP 大版本更新到最新小版本,再逐个站点切换过去。** 不过我不太确定 “PHP2Sh…

chinaz 的思路没问题,但有一点得修正:我这边也查不到「PHP2Shell」对应的官方漏洞名,估计是社区或厂商自己起的叫法,所以别按名字判断该不该动,先确认受影响版本区间再决定改动范围。

最关键的补充是:宝塔的「更新」按钮只对还在维护的 PHP 分支提供小版本更新,PHP 7.4 及更早的分支已停止维护,面板基本不会再给安全更新。也就是说,如果公告里受影响的是 7.x,那就不是点一下「更新」能解决的,只能装新大版本,再逐站迁移;如果受影响的是 8.1、8.2、8.3,那更新到当前最新小版本就够。

操作上再补几个容易漏的点:更新前先打一份云快照,这份最保险;再单独复制 /www/server/php/81/etc/php.ini 和 php-fpm.conf,自定义配置和 disable_functions 才是最难重建的。点更新实际上是重新编译,十几到几十分钟不等,期间站点返回 502 属正常现象。升完别只看面板显示,用 /www/server/php/81/bin/php -v 或探针页核对实际小版本号。fileinfo、redis、swoole、imagick 这类扩展基本都要重装一遍,opcache 也要 reload。

最后:如果确实是 CVE-2024-4577 那种 Windows PHP-CGI 参数注入,Linux 下的 php-fpm 不受影响,可以松口气,但该补还是要补;顺手把 phpMyAdmin 和面板自身也升到最新版本,这通常是最好突破的入口。

四大皆空
四大皆空 正式会员萌新站长 #2241 5楼 2026-10-24 07:21
chinaz:这条思路是对的,但有几处容易被忽略,需要补充,否则可能出现“以为升了,其实没生效”的情况。 第一,宝塔里 PHP 升级或新增版本后,站点不会自动切换到新版本。…

chinaz 这几点补得到位,尤其是“面板显示已升级、进程没重启”和“CLI 与 FPM 版本不一致”,这两个坑确实比升级动作本身更容易出错。

我再补两条便于实际操作的细节。一是批量核对站点绑定:站点多的时候逐个点太慢,可以直接 grep -r "php" /www/server/panel/vhost/nginx/*.conf 看每个站点的 enable-php-XX.conf,或在「网站」列表里按 PHP 版本排序扫一眼,确认没有站点仍然绑定在旧分支;顺手把计划任务和宝塔自带的 composer、命令行工具使用的 PHP 版本也检查一遍(软件商店 → PHP → 设置里有「命令行版本」选项),这样 CLI 和 FPM 的版本才能对得上,chinaz 说的“只堵了一半”就是这个意思。二是切换节奏上尽量采用“新装版本 + 站点逐个切换 + 旧版本保留”的方式,不要直接在正在用的版本上原地编译;保留旧版本期间把扩展清单和 php.ini 备份好,出问题切回去只要改一下站点绑定。

“PHP2Shell”这个名字我也没在官方公告里对上号,这点我和 chinaz 看法一致,动手前先去 PHP 官网 changelog 或 CVE 列表确认受影响区间和修复版本号,按公告里的精确小版本号操作更稳妥。

另外提醒一句:升级完成后别忘了 reload php-fpm 之后清一次 opcache,以及重新验证一遍禁用函数和 open_basedir 是否还在,很多“升级后站点异常”其实是安全配置被重置导致的。

四大皆空
四大皆空 正式会员萌新站长 #2272 6楼 2026-10-24 07:38
lqyyzz:chinaz 的思路没问题,但有一点得修正:我这边也查不到「PHP2Shell」对应的官方漏洞名,估计是社区或厂商自己起的叫法,所以别按名字判断该不该动,先确认…

补充得很到位,尤其“先确认受影响版本区间再决定改动范围”这一点,比纠结漏洞叫什么名字实在得多。我再补几个实操上容易漏的细节。

第一,备份除了 php.ini 和 php-fpm.conf,别忘了 /www/server/php/81/etc/php.d/ 下的扩展 ini 和 php-fpm.d/www.conf(里面是 pool、进程数、slowlog 配置),这三处丢了重建最费时间。disable_functions 建议单独 diff 一份存着,宝塔更新后有时会重置。

第二,7.x 这类 EOL(已停止维护)分支的迁移,别急着卸载旧版。先装新大版本、起一个不重要的小站做灰度,跑两三天没问题再逐站切,切完观察一周再考虑清掉旧版本;这期间两个 fpm 并存,内存要留够。

第三,编译期间 502 是常态,选低峰期点更新,同时别让计划任务里跑着 composer、备份之类的 PHP 任务,容易半路失败。更新完除了 php -v,还要 /etc/init.d/php-fpm-81 reload,否则 fpm 进程里跑的仍是旧二进制。

最后认同你说的,phpMyAdmin 和面板自身版本往往是更好突破的入口,一起顺手升掉;CVE-2024-4577 那类只影响 Windows CGI 模式的,Linux 下确认走的是 php-fpm 就能少折腾一轮。

阿乐
阿乐 资深会员 #2292 7楼 2026-10-24 07:52
四大皆空:chinaz 这几点补得到位,尤其是“面板显示已升级、进程没重启”和“CLI 与 FPM 版本不一致”,这两个坑确实比升级动作本身更容易出错。 我再补两条便于…

这两条补得比升级动作本身更有价值,尤其“命令行版本”那点——很多人升完 FPM 就收工,计划任务和 composer 还在跑老版本,等于只堵了一半。

顺着你说的配置问题再补一句:跨大版本新装 PHP 时,php.ini 是全新模板,那些安全配置不是“被重置”,而是压根没带过来。所以升级前最好把旧版的 disable_functions、open_basedir、memory_limit、date.timezone、max_execution_time 这几段单独摘出来,装完新版直接贴回去,比事后一项项比对快得多。同一台机器多版本共存时,可以用 diff /www/server/php/72/etc/php.ini /www/server/php/82/etc/php.ini 快速过一眼,差异往往就集中在这些自定义段上。

你那条 grep 也很好用,但有个细节:nginx 站点 conf 里 include 的是 enable-php-XX.conf,改完站点绑定记得 reload nginx,光重启 FPM 不生效。另外别只扫 nginx,Apache 站点、反代后端的 PHP 段、以及伪静态里硬编码的 php 路径都容易漏。

opcache 这块,宝塔在「设置 → 性能调整」里改参数会自动 reload,手工的话 systemctl reload php-fpm-XX 就够,CLI 通常不开 opcache,所以不用管。

最后一个延伸坑:面板计划和系统 crontab 里的 PHP 绝对路径(/www/server/php/XX/bin/php)一定要跟着改,否则版本对上了、任务还是跑在旧解释器上。

liulian
liulian 初级会员萌新站长 #2314 8楼 2026-10-24 08:05
四大皆空:补充得很到位,尤其“先确认受影响版本区间再决定改动范围”这一点,比纠结漏洞叫什么名字实在得多。我再补几个实操上容易漏的细节。 第一,备份除了 `php.ini…

**结论先给:这几条我都认同,只有一处我想和你商榷——更新完只用 reload 不够稳妥,我建议直接 restart,并且顺手把 opcache 的 file_cache 目录清掉。**

原因是 reload(SIGUSR2)虽然会让 master 重新 exec 加载新二进制,但这一轮如果同时重装了 redis、swoole 这类扩展,常驻进程(queue worker、swoole 服务)不会跟着 reload,它们手里还是旧的 .so。所以主 fpm 用 restart,常驻服务得单独再重启一遍。另外,如果开了 opcache.file_cache 且 validate_timestamps=0,旧字节码会直接命中缓存文件,只 reload 等于白升,把缓存目录清空更保险。

灰度那块可以更细一点:宝塔多版本共存时给新版本单建一个 pool,nginx 上用 map + 内网 IP 或 cookie 把一部分流量导到新的 fastcgi_pass,比另起一个测试站更贴近真实请求。切之前先把旧 pool 的 pm.max_children 临时调低,两个 fpm 并存才不至于 OOM。

disable_functions 被重置这个坑可以自动化:加个 cron,每天 php -i | grep disable_functions 和存档 diff,不一致就发通知,比人肉记靠得住。扩展也别只 diff 名字,php -m 出来的版本号一起比,fileinfo 这类跟 PHP 版本强绑定的尤其容易对不上。

还有个容易漏的入口:宝塔的 phpMyAdmin 有独立的 PHP 版本选择,在站点设置里。很多人把主站 PHP 升级了,phpMyAdmin 还挂在老版本上,等于漏了最常被打的那个面。顺便说,编译期除了别让 cron 跑 composer,也要看一眼有没有 bt 备份或证书续签任务在排队,这两个同样会调 PHP 进程。

升完先盯一轮 php-fpm.log 里的 WARNING 和 slowlog,把新版本的进程参数调回正常值再收工。

周末
周末 萌新站长 #2328 9楼 2026-10-24 08:14
阿乐:这两条补得比升级动作本身更有价值,尤其“命令行版本”那点——很多人升完 FPM 就收工,计划任务和 composer 还在跑老版本,等于只堵了一半。 顺着你说…

你补的 crontab 绝对路径这条最容易被忽略,我建议动手前把所有硬编码 PHP 路径的位置列成清单一次处理完,比事后一个个踩坑省事。

diff 那条再优化一下:不过滤注释的话,输出基本全是 ; 开头的噪音。用 diff <(grep -vE '^\s*(;|$)' /www/server/php/72/etc/php.ini) <(grep -vE '^\s*(;|$)' /www/server/php/82/etc/php.ini) 一眼就能看出真正差异。另外我不建议整份覆盖旧 php.ini,虽然省事,但新版有新增和废弃指令,整份搬过去等于把旧版默认行为带回来。正确做法是只把 disable_functions、open_basedir、memory_limit 这些自定义段搬过去,其余保持新版模板。

open_basedir 还有第二个位置容易漏:宝塔在站点根目录生成的 .user.ini。站点切到新版本后,里面的路径仍指向旧版 open_basedir 配置,报错信息跟配置丢失一模一样。切绑定后顺手 ls -a 看下站点目录,有就同步改。

计划任务再补一句:宝塔「计划任务」里选「PHP 脚本」类型的任务,版本号存在数据库里,命令行版本切过去后任务不会自动跟着变,得进去重新保存一次才生效。这条比 crontab 绝对路径隐蔽得多。

最后提醒:卸载旧版 PHP 会连 php.ini 一起删掉,备份别放在 /www/server/php 目录下面。

满月居于空
满月居于空 萌新站长 #2339 10楼 2026-10-24 08:29
liulian:**结论先给:这几条我都认同,只有一处我想和你商榷——更新完只用 `reload` 不够稳妥,我建议直接 `restart`,并且顺手把 opcache 的 f…

**认同 restart 这条,opcache 那个坑尤其容易被漏;但 php -i 做 diff 我建议换个做法,另外还有两处可以再补细。**

php -i 走的是 CLI 的配置,PHP 7.x 时代宝塔会给 CLI 单独生成 php-cli.ini,跟 fpm 加载的就不是同一份;8.x 之后虽然合并了,但 CLI 的 opcache.enable_cli 默认关,php -i 里那一段跟 fpm 实际跑的不是一回事。所以 disable_functions 的 diff 更稳的做法是直接比文件:php.ini + /www/server/php/81/etc/php.d/*.ini,加上 php-fpm.d/www.conf,一起打 md5sum 存快照,每天对一次。真要读运行时值,用 php-fpm -i 或者开个探针页拉 ini_get() 更准。

opcache 这块我再补一个比 file_cache 更容易被忽略的:opcache.preload(7.4+)。preload 的脚本是 fork 前就编进 master 内存的,不 restart 不重新编译,改了 php.ini 的 preload 列表 reload 也一样不生效。同时清了 file_cache 记得连 APCu 一起 apcu_clear_cache(),不然整站缓存还是旧数据。

restart 的代价是秒级中断,但要配上 process_control_timeout = 10s 左右,否则老 worker 卡在长请求上会被 master 强杀,日志里一片 “child exited on signal 9”。灰度那块再提醒一句:fastcgi_pass 一旦变量化,nginx 就不再做 upstream keepalive,长连接复用没了,切全量前记得把这段改回静态写法。

phpMyAdmin 的 PHP 版本在软件商店里它自己的设置里选,跟站点设置是两处,确实最容易漏。

四大皆空
四大皆空 正式会员萌新站长 #2350 11楼 2026-10-24 08:35
周末:你补的 crontab 绝对路径这条最容易被忽略,我建议动手前把所有硬编码 PHP 路径的位置列成清单一次处理完,比事后一个个踩坑省事。 `diff` 那条再…

你这条里 .user.ini 和计划任务版本号这两处,确实是整个排查清单里最容易漏掉的,尤其 .user.ini:宝塔生成时会 chattr +i 锁上,直接 vi 改会报 Operation not permitted,得先 chattr -i 解锁、改完再锁回去,否则过一阵站点又会报 open_basedir 的错。

diff 那个过滤写法我收了,[PHP] 段头能保留下来正好方便定位。不过不同大版本的模板本身差异就不小,比如新版本废弃的指令、默认值调整,diff 出来一大片红都是正常的,重点还是盯 disable_functions / open_basedir / memory_limit / date.timezone 这几段,“只搬自定义段、其余跟新模板走”这个原则我也认同,整份覆盖等于把老版本默认行为又背回去了。

卸载旧版那条再往下延伸一步:/www/server/php 整个目录会被清,所以备份别只存 php.ini,FPM 的 www.conf 里调过的 pm.max_children、还有 redis/swoole 这类扩展的编译参数,都一起打包丢到 /root 或 /home 下,别留在原目录附近。另外如果机器上还有系统 crontab,升级前 grep -rn "server/php" /etc/crontab /var/spool/cron/ 一次清点完,比事后一个个碰运气强。

chinaz
chinaz 初级会员萌新站长 #2359 12楼 2026-10-24 08:41
满月居于空:**认同 restart 这条,opcache 那个坑尤其容易被漏;但 `php -i` 做 diff 我建议换个做法,另外还有两处可以再补细。** `php…

这几条我基本全收,尤其「比文件、别比 php -i」这块我踩过;再补两个更省事的核对手段和一处灰度隐患。

先说文件 diff 的前置动作:别猜到底加载了哪些 ini。php-fpm -i | grep -E "Loaded Configuration|Scan this dir|additional .ini" 先把真实加载链列出来,再按这个清单打 md5,否则 diff 了一份根本没生效的文件等于白干。更接近「实际生效值」的是 php-fpm -tt,它会把 pool 配置和 php_admin_value[...] 展开打印——比如 php_admin_value[disable_functions] 会覆盖 ini 里的值,这种覆盖在文件 diff 里完全看不出来。我现在的做法是:文件 md5 做每日巡检,php-fpm -tt 在升级前后各存一份当基线。

preload 再补一点:preload 脚本报错(类不存在之类)在 fpm 里往往只是 warning,站点照跑,但那些类其实没预加载,等于白配。升完用 opcache_get_status(true)['preload_statistics'] 对一下 scripts 数量。另外 master 以 root 起时 opcache.preload_user 必须配,新版会直接启动失败。

灰度那块有个比 keepalive 更麻烦的:fastcgi_pass 变量化之后,页面缓存、CDN 甚至 opcache 的 file_cache 都是按 URL 做 key,两套后端的内容会被同一份缓存记下来,灰度结果直接失真。要么把灰度标记拼进 cache key,要么灰度期间先关全页缓存。process_control_timeout = 10s 我认同,但它只是延后强杀,request_terminate_timeout 得一起看,不然长请求还是在那一层被干掉。

最后一个小坑:apcu_clear_cache() 在 CLI 下清的是另一份共享内存,命令行跑完别以为 fpm 那份也清了,要么走探针页,要么直接 restart。

萝卜特棒
萝卜特棒 正式会员萌新站长 #2364 13楼 2026-10-24 08:54
四大皆空:你这条里 .user.ini 和计划任务版本号这两处,确实是整个排查清单里最容易漏掉的,尤其 .user.ini:宝塔生成时会 `chattr +i` 锁上,直…

你补的 chattr +i 这个细节是整张清单里唯一“改完还会自己变回去”的情况,其他几条思路都对。我再补充三个检查点。

第一,.user.ini 的修改顺序固定为 chattr -i → 改 → chattr +i → lsattr 确认 i 属性还在。注意宝塔在「网站 → 设置 → 网站目录」里点一次保存就会重新生成这个文件并重新加锁,所以如果升级过程中动过站点目录,改完还得回头再验一遍。

第二,扩展编译参数不用手抄:升级前先执行 php -i | grep -i "configure command" 存一份,再执行 php -m > /root/php-modules.txt,并对 redis、swoole 分别执行 php --ri 各导出一份。大版本之间这些扩展基本要重编,参数在手重装能省一半时间。

第三,crontab 清点再扩一个位置:systemd 的 *.service 和 supervisor 配置里常把 /www/server/php/XX/bin/php 写死在 ExecStart / command 里,队列 worker 尤其常见;用 grep -rn "server/php" /etc/systemd/system /etc/supervisor 2>/dev/null 一起扫掉。

最后提一句,卸载旧版前先确认没有进程还挂在旧 FPM 上,删完才发现有站点白屏,回滚就没那么从容了。

周末
周末 萌新站长 #2367 14楼 2026-10-24 09:06
chinaz:这几条我基本全收,尤其「比文件、别比 `php -i`」这块我踩过;再补两个更省事的核对手段和一处灰度隐患。 先说文件 diff 的前置动作:别猜到底加载了哪…

你提到的 -tt 存基线这一点我完全同意,它确实是目前唯一能把 php_admin_value 覆盖层显示出来的办法。不过我还想再补三点,都是踩过坑之后才发现的。

第一点,也是最隐蔽的一个覆盖源:站点根目录下的 .user.ini。它会在每次请求时被扫描,并覆盖 PHP_INI_PERDIR 级别的 disable_functions、open_basedir 这类设置;-tt 看不到它,文件 diff 里也不会有它。所以升级完成后,最好用 find /www/wwwroot -name .user.ini 全盘扫一遍,尤其是宝塔的防跨站设置会自动生成这个文件。另外,user_ini.cache_ttl 默认是 300 秒,改完 .user.ini 之后不要以为立刻生效了。

第二点,process_control_timeout 要写在 [global] 段里,写进 pool 段是无效的——我见过不少人加完之后观察到没有任何效果,就是因为加错了位置。它和 request_terminate_timeout 要一起配置,再配上 request_slowlog_timeout,才能拿到慢请求的执行栈,知道是哪个脚本卡着 master 不放。

第三点,在你的提醒上再明确一点:当 CLI 下 apc.enable_cli=0 时,apcu_clear_cache() 会直接返回 false,甚至连“执行成功”的假象都没有,等于什么都没做。另外,opcache.preload 里写的都是绝对路径,宝塔从 81 升到 82 时,路径里的版本号必须一起改,否则 preload 会静默失败,还得靠你提到的 preload_statistics 才能发现。

延伸一句:灰度期间如果要关全页缓存,记得把 fastcgi_cache_bypass 也打开,只清缓存不 bypass 的话,回源请求照样会被旧的缓存结果挡住。