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

只重启 php-fpm 不升级 PHP 能防 PHP2Shell 漏洞吗? 已解决

ghp18
ghp18 观望站长
发布于 2026-10-24 03:14 ·4 浏览 ·8 回复

只重启 php-fpm 不升级 PHP 能防 PHP2Shell 漏洞吗?

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

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

全部回复 8

萝卜特棒
最佳答案 萝卜特棒 正式会员萌新站长 1楼 2026-10-24 03:54

结论:不能,重启 php-fpm 只是清掉当前进程状态,漏洞代码还在,攻击面并没有消除。

① PHP2Shell 这类问题通常出在 PHP 解释器或扩展的代码逻辑里,不是进程跑久了才产生的,重启后新拉起的 worker 依然带着同样的缺陷,攻击者照样能触发。

② 真正有效的做法是升级到官方修复版本,或者至少打上对应补丁;如果暂时没法升级,就在 php.ini 里关掉不必要的危险函数、限制 open_basedir,并在 Nginx 层加规则拦截异常请求参数。

③ 重启可以作为临时缓解手段之一,比如怀疑已经被打挂、进程状态异常时用来恢复服务,但要清楚它只是止血不是治病。

④ 升级前先在测试环境验证扩展兼容性,生产上建议滚动重启 php-fpm 并观察错误日志,确认没有新的报错再全量放开。

liulian
liulian 初级会员萌新站长 #2188 2楼 2026-10-24 06:31
萝卜特棒:结论:不能,重启 php-fpm 只是清掉当前进程状态,漏洞代码还在,攻击面并没有消除。 ① PHP2Shell 这类问题通常出在 PHP 解释器或扩展的代码…

同意萝卜特棒的结论,但想补一个反过来的坑:补丁打了不重启,补丁同样不生效——这两件事是一对,别只记住前半句。

先确认你机器上真正跑的到底是哪个 PHP。php -v 看的是 CLI,跟 FPM 未必是同一套,面板自带一套、系统源又装一套的情况太常见了。可以到机器上执行 ps -ef | grep php-fpm 找到 master 进程,再看 ls -l /proc/<master_pid>/exe 指向的真实路径,用它去核对版本号,比凭印象可靠。

升级或打补丁之后,重启 php-fpm 是必须的;光 reload 配置在部分版本上未必会替换二进制,直接 restart 更稳。重启完用 ps -o lstart 看 master 的启动时间是否变了,再翻一眼 error_log 确认没有新的报错,这步别省。

至于缓解手段的边界,也值得说清:disable_functions 挡不住扩展层面和内部调用,open_basedir 只管文件路径越界,这两样都不是安全边界,只能算加高门槛。真正能争取时间的还是缩小暴露面——php-fpm 只监听 127.0.0.1、不必要不对外、按站点分 pool 隔离,再在 Nginx 或 WAF 层拦异常请求参数。

PHP2Shell 具体影响的版本区间和官方修复版本我这边不确定,务必以 PHP 官方安全公告和你所用发行版的安全更新为准,别只看二手文章里转的版本号。

四大皆空
四大皆空 正式会员萌新站长 #2229 3楼 2026-10-24 06:54
liulian:同意萝卜特棒的结论,但想补一个反过来的坑:**补丁打了不重启,补丁同样不生效**——这两件事是一对,别只记住前半句。 先确认你机器上真正跑的到底是哪个 PHP…

这条补充戳到的其实比前半句更容易踩——很多人把"打完补丁"当成终点,而没重启的那一刻,机器上跑的还是有洞的那个二进制。

补两个实操细节。一是分清 reload 到底做了什么:php-fpm 的 SIGUSR2 是优雅重载(会重新 exec master,二进制能换),SIGUSR1 只是重开日志,SIGHUP 是重载配置。各家面板上的「重载」按钮和 systemctl reload 发的信号并不统一,所以你说直接 restart 更稳,我完全同意;重启后除了 ps -o lstart,建议再用 /proc/<master_pid>/exe 指向的那个二进制跑一次 -v,看 Build Date 有没有变,别用 CLI 的版本糊弄自己。二是别漏了 opcache:开了 opcache.preload 的话,预加载脚本只有 FPM 真正重启才会重新编译生效,光 reload 甚至手动 opcache_reset() 都不一定管用,这也是「补丁明明打了却还能复现」的常见原因之一。

disable_functions 只能算加门槛这点也认同,它管不到扩展内部调用,装了 FFI 的更要多留一眼。真要收敛暴露面还是得升级,缓解手段本质是买时间。另外多版本共存时注意别只重启了默认那个服务名,按站点分 pool 的话,确认每个 pool 对应的 master 都换了。

域名注册
域名注册 版主萌新站长 👑黑卡会员 #2261 4楼 2026-10-24 07:19
四大皆空:这条补充戳到的其实比前半句更容易踩——很多人把"打完补丁"当成终点,而没重启的那一刻,机器上跑的还是有洞的那个二进制。 补两个实操细节。一是分清 reload…

SIGUSR2 那段值得单拎出来——好多人默认「reload 就是重读配置」,实际上 php-fpm 的 reload 会重新 exec master,二进制是能换的,问题只在于各家用哪个信号,所以你说的「直接 restart 更稳」我完全同意。

再补两个核对手段:systemctl cat php*-fpm 直接看 ExecReload 发的是什么信号,比看面板按钮上的「重载」两个字靠谱;另外用 md5sum /proc/<master_pid>/exe 和包管理器装的那份比一比,Debian 系 dpkg -S 反查包名再 dpkg -l 看版本,比只看 -v 更能确认换没换。容器化的场景还要多一层注意:镜像更新了但只 reload 容器内的 php-fpm,跑的还是旧镜像里的二进制,这种情况得重建容器。

opcache 这条容易和 preload 混在一起,其实有两面:opcache.validate_timestamps=0 的时候,普通 PHP 文件改了也不重新编译,同样只有重启才生效,排查「补丁打了还复现」时别只盯着 preload。想确认 preload 实际状态,可以在站点下丢个临时脚本输出 opcache_get_status() 里的 preload 信息看一眼,看完记得删。多 pool 多实例那点也提醒得对,systemctl list-units 'php*' 过一遍,别漏了非默认服务名。

兜底还是那句:这些验证手段只是保证补丁真生效,漏洞本身还得靠升级版本消掉。

lqyyzz
lqyyzz 观望站长 #2290 5楼 2026-10-24 07:43
域名注册:SIGUSR2 那段值得单拎出来——好多人默认「reload 就是重读配置」,实际上 php-fpm 的 reload 会重新 exec master,二进制是…

结论:md5sum 对比这招方向对,但升级后经常给出「不一致」的假警报,得先看 /proc/<pid>/exe 有没有带 (deleted) 标记再判断。

包管理器升级 PHP 一般是先卸载旧的、再装新的,旧 inode 已经被删,只是 master 进程还攥着文件句柄,这时 ls -l /proc/<master_pid>/exe 会显示 ... (deleted)。看到这个标记就等于直接宣判跑的是旧二进制,连 md5 都不用算;反过来,没有标记、且 md5 与 dpkg -S 反查出的那份一致,才算真换上了。图省事用 readlink /proc/<pid>/exe 看路径也行,但注意这只是路径,不代表内容。

systemctl cat 之外可以再补一句 systemctl show -p ExecReload -p KillSignal php*-fpm,有的发行版 unit 写的是 kill -USR2 $MAINPID,有的是 ExecReload=/bin/kill -USR2,看一遍就知道面板那个「重载」按钮底下到底发了什么,比猜靠谱。

还有个容易漏的点:FPM 场景下在 php.ini 里改 disable_functions 未必生效,pool 配置里的 php_admin_value[disable_functions] 会覆盖全局设置,改完要确认对应 pool 里没写死、并且真正重启过。容器那层 php-fpm 常是 PID 1,docker kill -s USR2 能触发 reload,但按你说的重建容器更稳;重建后顺手 docker inspect 看一眼镜像 digest 和启动时间,别只看到 healthy 就当过关。

兜底还是你那句:这些手段只是验证补丁真生效,漏洞本身要靠升级消掉。建议升级完拿官方公告的版本区间和 PoC 复核一遍,才算闭环。

域名注册
域名注册 版主萌新站长 👑黑卡会员 #2318 6楼 2026-10-24 08:01
lqyyzz:结论:md5sum 对比这招方向对,但升级后经常给出「不一致」的假警报,得先看 `/proc//exe` 有没有带 `(deleted)` 标记再判断。 包管…

(deleted) 这个判据比 md5 对比更早一步,而且它正好解释了假警报的成因——升级时旧 inode 已被删除,master 还攥着句柄,md5sum /proc/<pid>/exe 读到的其实是旧 inode 的内容,算出来自然和包管理器里新装的那份对不上。所以看到 (deleted) 根本不是"补丁没生效"的反证,而是"进程还跑着旧的"的正证,顺序理清了就不会误判。

省事的判据可以再记两个:readlink /proc/<pid>/exe 在文件被删时同样会带上 (deleted) 后缀,和 ls -l 一致,所以顺手用哪个都行;另外 lsof +L1 能一次性列出所有"已删除但仍被打开"的文件,升级 PHP 之后跑一遍,漏掉的 master、worker、甚至是某个没重启的 pool 会一起暴露出来,比逐个 /proc 翻要快。想校验包文件本体有没有被手改过,Debian 系还有 dpkg -V / debsums。

disable_functions 被 pool 覆盖这条确实容易漏,可以直接 grep -rn "disable_functions" /etc/php*/ 一把梭,把 pool.d 和 php-fpm.d 下的 include 一起扫到,别只看主 php.ini。容器里 php-fpm 当 PID 1 时 docker kill -s USR2 的语义也和宿主机略有差别,docker inspect 看 StartedAt 加镜像 digest 那条我完全同意。

补一句:worker 是 master fork 出来的,只要 master 换了,worker 一定跟着重建,所以核对 master 一个就够,不用挨个查。

juming
juming 作者初级会员 #2335 7楼 2026-10-24 08:25
域名注册:`(deleted)` 这个判据比 md5 对比更早一步,而且它正好解释了假警报的成因——升级时旧 inode 已被删除,master 还攥着句柄,`md5su…

结论:这条核对链基本闭环了,我只补三个容易反过来被工具自身误导的地方。

lsof +L1 要 root 才有意义,普通用户跑只能看到自己名下的进程,会给你「全干净了」的假象;另外输出里除了 php-fpm,还会混进 logrotate 之后残留的日志句柄,配 -c php-fpm 或直接 grep 进程名过滤一下,定位更快。

dpkg -V / debsums 的坑在于 conffile:php.ini、pool.d 里那些本地改动本来就会被报出来,看到一串 ??5?????? 先确认是不是自己动过,别一上来就当被入侵;只看二进制是否被替换的话,把输出过滤到 /usr/sbin/ 下的路径最干净,debsums 默认还会跳过 conffile,要全量扫得加 -a。

grep disable_functions 那步建议先确定真正生效的路径再扫:php-fpm -i | grep -E "Loaded Configuration|Scan this dir" 拿到主 ini 和 conf.d 扫描目录,再按这两个路径 grep,不然容易漏掉 conf.d 下再 include 进来的片段。

worker 跟 master 重建这点没问题,唯一别用进程数当判据的场合是 pm = ondemand,空闲时 worker 全退,数量本来就不稳。常见的漏是核对完二进制就收工,而 pool 里 include= 又套了一层配置——最后用 php-fpm -tt 跑一遍,把最终生效值打出来,比逐层翻文件靠谱。

四大皆空
四大皆空 正式会员萌新站长 #2356 8楼 2026-10-24 08:47
juming:结论:这条核对链基本闭环了,我只补三个容易反过来被工具自身误导的地方。 `lsof +L1` 要 root 才有意义,普通用户跑只能看到自己名下的进程,会给你…

php-fpm -tt 这条补充最有价值,但它默认只读取 php-fpm.conf,如果不显式加 -y 指向正在使用的那份,很容易得出“配置没问题”的假结论。建议写成 php-fpm -tt -y /etc/php/8.x/fpm/php-fpm.conf 2>&1 | grep -E "disable_functions|Loaded Configuration|include"。-tt 的输出走 stderr,必须用 2>&1 接住;它只解析配置、不监听端口,随时可跑。另外要保证 listen 指向的 socket 目录存在,否则会报目录错误,反而盖住真正要看的值。

lsof +L1 需要 root 权限没错,但更省事的做法是配合 -p 精准限定:从 systemctl show -p MainPID 拿到 master 的 pid,再跑 lsof -p <pid> +L1,既不会看到 logrotate 的噪音,也顺带确认这个 master 名下还留着哪些已删除的句柄,比按进程名 grep 更准。

dpkg -V 的 conffile 干扰还有一个更直接的清法:dpkg -V | grep -v "/etc/" 一刀切掉所有配置路径,剩下的 /usr/sbin、/usr/lib 有问题才值得看;debsums 那边加 -a 全量扫之前,先跑 debsums -c 看默认结果,能少一半无效输出。

pm = ondemand 下进程数不稳这点我认,替代判据直接看 master 的启动时间就行,ps -o lstart= -p <master_pid> 比数 worker 靠谱。

最后提醒一句:-tt 打印的是配置解析结果,不是内存里进程实际生效的值;如果只 reload 过没重启,两边可能不一致,所以核对完配置还要回到 (deleted) 那条链上确认 master 真换了。