首页
社区
课程
招聘
[原创]PHP2Shell 复现连载(四):加固、自查与应急响应
发表于: 7小时前 64

[原创]PHP2Shell 复现连载(四):加固、自查与应急响应

7小时前
64

上一篇(第三篇):https://bbs.kanxue.com/thread-293431.htm
PHP2Shell 复现连载(四):加固、自查与应急响应

【测试声明】本篇为防守向内容,所有命令用于自有系统的检查与修复。

0x00 定位

这一篇面向运维和开发:怎么确认自己是否受影响、怎么修、修完怎么验证、怀疑已被打进来之后按什么顺序处理。

前四篇讲原理,这一篇讲动作。如果只看一篇,看这篇。

0x01 判断是否受影响(三件事,按顺序做)

0x01.1 第一步:看版本

php -v

对照下表:

8.5 分支受影响至 8.5.11,修复版本 8.5.12+
8.4 分支受影响至 8.4.26,修复版本 8.4.27+
8.3 分支受影响至 8.3.35,修复版本 8.3.36+
8.2 分支受影响至 8.2.34,修复版本 8.2.35+
8.1 及以下全部受影响,官方已 EOL,没有修复版本

只要版本号低于修复版本,就处于受影响范围。这跟业务代码写得好不好无关,是 PHP 运行时的缺陷。

顺便提醒:8.1 及以下没有修复版本,唯一的出路是升级分支。继续用 EOL 版本,将来还会遇到别的洞。

0x01.2 第二步:看配置(决定攻击面是否存在)

php -i | grep -i "session.upload_progress"

输出:

session.upload_progress.enabled => On => On
session.upload_progress.cleanup => On => On
session.upload_progress.name => PHP_SESSION_UPLOAD_PROGRESS => PHP_SESSION_UPLOAD_PROGRESS
session.upload_progress.freq => 1% => 1%
session.upload_progress.min_freq => 1 => 1

enabled => On 表示攻击面存在。注意这是 PHP 的默认值,也就是说绝大多数安装都是开着的,没有人主动打开过它。

0x01.3 第三步:看暴露面(决定风险等级)

ss -lntp | grep -E "9000|9001|9002|9003"

判读:

输出 127.0.0.1:9000,风险低,仅本机可达;
输出 0.0.0.0:9000 或 [::]:9000,风险高,需立即收敛;
输出 unix socket,风险低;
无输出,正常。

Docker 环境额外查一条:

docker ps --format "table {{.Names}}\t{{.Ports}}"

如果看到 0.0.0.0:9000->9000/tcp,说明 fpm 端口被映射到宿主机所有网卡。这是我在第四篇里反复强调的那个点,也是实际中最常见的失误。

为什么把这一步放在版本检查之后单独强调:因为它决定的是"能不能被打"而不是"有没有洞"。一个版本落后但 fpm 只听本机的站,和一个版本最新但 9000 对着公网的站,后者的即时风险更高。

0x02 修复方案(五个,按优先级排)

0x02.1 方案一:升级 PHP(首选,也是唯一彻底的)

Debian / Ubuntu 用官方源:

apt-get update
apt-get install --only-upgrade php8.4-fpm php8.4-cli php8.4-common

升级后必须重启 FPM,否则旧进程还在跑:

systemctl restart php8.4-fpm
systemctl status php8.4-fpm

Docker 环境注意:只 restart 容器不够,必须重建镜像再重新部署,否则跑的还是旧镜像。

验证:

php -v | head -1

版本号达到修复版本即可。

0x02.2 方案二:关闭 upload_progress(临时缓解)

无法立即升级时的过渡措施。编辑 php.ini:

session.upload_progress.enabled = Off

重启 FPM 生效:

systemctl restart php8.4-fpm

副作用:依赖上传进度条的前端会失效。如果站点确实用到,需要评估业务影响再决定——但注意,这个评估不应该拖太久,因为真正的修复是升级。

验证:

php -i | grep "session.upload_progress.enabled"

session.upload_progress.enabled => Off => Off

0x02.3 方案三:收敛 FPM 监听地址

即使不打补丁,把 fpm 关进本机也能大幅降低风险。改 pool 配置:

; /etc/php/8.4/fpm/pool.d/68cN6%4N6%4i4K6u0W2j5$3!0F1k6R3`.`.
listen = 127.0.0.1:9000

或改用 unix socket,性能更好,且根本不走 TCP:

listen = /run/php/php8.4-fpm.sock

nginx 侧同步改:

fastcgi_pass unix:/run/php/php8.4-fpm.sock;

这一条我认为性价比最高:改动小、无业务影响、当天就能做完,而且顺带解决了一整类"fpm 裸奔"问题,不只是这一个 CVE。

0x02.4 方案四:保持 nginx 请求缓冲(纵深防御)

fastcgi_request_buffering on;

这是 nginx 的默认值。开启后 nginx 会把完整请求体收齐再转发给 FPM,攻击者无法制造"两次数据事件之间的停顿",UAF 的触发条件被破坏(原理见第二篇的 0x05)。

注意:靶场为了复现,特意设成了 off。生产环境不要照抄靶场配置——这一条听起来像废话,但我在真实环境里见过不止一次"从靶场配置直接抄到线上"的情况。

0x02.5 方案五:WAF 规则(补充,不是主力)

在边界拦异常 multipart,可用的判据:

单请求包含 PHP_SESSION_UPLOAD_PROGRESS 字段,且值中含 PHP 开标签;
同一 multipart 请求的分片间隔超过 1 秒;
请求体分片数异常。

这类规则能挡住自动化扫描,挡不住针对性攻击,只能作为补充。别把它当成主要防线。

0x03 修复后怎么验证

0x03.1 用靶场自带的缓解配置验证

靶场提供了 docker-compose.mitigated.yml,加载 php-mitigated.ini(关闭 upload_progress):

cd /root/php2shell/lab-uaf
docker compose -f docker-compose.yml -f docker-compose.mitigated.yml up -d

再跑原语验证:

python3 scripts/php2shell_poc.py --monitor --verify

预期:

upload_progress: DISABLED (mitigated)
Armed: NO - session empty (mitigation active or stall too short)

对照关系:

漏洞配置下 upload_progress 为 ENABLED,Armed 为 YES;
缓解配置下 upload_progress 为 DISABLED,Armed 为 NO。

这个对照很有用:它让你亲手确认"缓解措施确实改变了结果",而不是凭配置文件里那一行来判断。

回到漏洞配置:

docker compose up -d

0x03.2 生产环境验证要点

PHP 版本:php -v,要求不低于修复版本;
配置项:php -i | grep upload_progress,要求 Off,或版本已修复;
FPM 监听:ss -lntp | grep 9000,不允许出现 0.0.0.0;
容器端口:docker ps,不允许出现 0.0.0.0:9000->;
nginx 缓冲:grep fastcgi_request_buffering,要求 on 或未设置。

五条全过,才算闭环。只改配置文件不重启、只重启不验证,都是半成品。

0x04 应急响应流程

如果怀疑已经被打进来,按这个顺序。

0x04.1 第一步:止血(先断,再取证)

不要先取证。先把暴露面关掉:

sed -i 's/^session.upload_progress.enabled./session.upload_progress.enabled = Off/' /etc/php//fpm/php.ini
systemctl restart php*-fpm

并在边界封禁 fpm 端口。

顺序很重要:取证是事后的事,止血是当下的事。反过来做,等于给攻击者留时间。

0x04.2 第二步:排查痕迹

session 目录里的异常文件:

ls -la /var/lib/php/sessions/ | head -50
grep -rl "<?php" /var/lib/php/sessions/ 2>/dev/null

这两条里第二条更关键——session 文件里本来不应该出现 PHP 开标签。有,就说明被投毒过。

Web 访问日志里的异常 multipart:

grep -i "multipart/form-data" /var/log/nginx/access.log | tail -100

PHP 错误日志里的 include 痕迹:

grep -i "auto_prepend_file|Failed opening required" /var/log/php*-fpm.log

第四篇里提到的那行 "Cannot modify header information - headers already sent by (output started at .../sess_xxx:1)",是执行成功的直接物证。看到就按已失陷处理。

0x04.3 第三步:假设已被控制

一旦确认命令执行,应当把主机视为已失陷,而不是"改回来就行":

不要只删 webshell 就恢复服务;
检查 crontab、~/.ssh/authorized_keys、systemd 服务里有没有新增项;
确认数据库连接配置是否被读取(这一步决定要不要轮换凭据);
查横向移动痕迹(内网连接记录、异常的出站流量)。

经验上,只删 webshell 不做这四步的,多数在几周内会二次失陷。

0x05 时间线建议

P0,24 小时内:关闭 upload_progress,或升级 PHP;
P0,24 小时内:把 fpm 端口收敛到 127.0.0.1;
P1,72 小时内:排查历史日志有无利用痕迹;
P1,72 小时内:确认 nginx 缓冲为默认开启;
P2,一周内:建立版本与配置基线巡检。

P0 那两条之所以都是 24 小时,是因为它们互相独立——升级没完成前,关配置就能挡住;配置改不了(业务依赖进度条)的,收敛端口也能挡住。

0x06 一句话总结

这个漏洞的特点不在技术难度,而在覆盖面:PHP 占服务端语言份额极高,session.upload_progress 又是默认开启,所以"存在攻击面"的主体数量非常大。

但它的实际杀伤力高度依赖部署方式。把 fpm 关进本机、保持 nginx 缓冲、及时升级——这三点做到,风险就基本可控。

反过来说,如果评估完得出"我们版本落后、配置默认、端口对着公网"这个结论,那就不要再讨论利用难度了,按已失陷的假设去做排查。

0x07 连载回顾

第一篇:漏洞原理与"能触发不等于能利用"的整体结论;
第二篇:从 session.c 源码定位根因,到 ZendMM 的 320 字节 bin;
第三篇:环境搭建、UAF 验证与两条路径的辨析;
第四篇:加固、自查、应急响应。

四篇里最该被转到群里的是这一篇。


免责声明:本文仅用于安全研究与防御加固讨论。任何测试须确保目标系统为自有资产或已获书面授权,测试环境与生产环境网络隔离。


冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。

收藏
点赞 ・0
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回