首页
社区
课程
招聘
[分享]PHP2Shell(CVE-2026-109618)UAF 路径分析与隔离环境复现记录
发表于: 2天前 371

[分享]PHP2Shell(CVE-2026-109618)UAF 路径分析与隔离环境复现记录

2天前
371

PHP2Shell(CVE-2026-109618)UAF 路径分析与隔离环境复现记录


【测试声明】

(1)本文全部验证均在本地隔离环境完成(127.0.0.1,Docker 单机靶场,与生产网络物理隔离),未对任何第三方系统发起请求;

(2)文中出现的脚本调用均指向官方公开仓库中的验证脚本,仅用于确认脆弱代码路径可达;

(3)本文不提供、也不讨论可直接武器化的利用工具;涉及链路细节的部分,目的是帮助防守方理解攻击面与前置条件;

(4)任何对非自有资产的测试行为均需获得书面授权,否则涉嫌违法。读者因不当使用产生的后果自行承担。




0x00 背景与结论


10 月 21 日 PHP 披露两个无需认证的 RCE 漏洞,代号 PHP2Shell,CVSS 4.0 评分 9.2。


我在 16G 内存的轻薄本上搭了隔离靶场做验证,结论和传播中的标题有出入:


一、UAF 原语很容易验证,3 轮全部命中;

二、官方 PoC 本身没有实现代码执行(README 的 Limitations 里写得很清楚);

三、我最终拿到的 uid=33(www-data),走的是另一条完全独立的路径,与 UAF 无关。


所以这篇文章的重点不是"打通了",而是把这两条路径拆开讲清楚:哪一条是漏洞本身,哪一条是配置问题。




0x01 漏洞成因:指针活得比容器久


漏洞编号:CVE-2026-109618 / CVE-2026-109619

漏洞类型:堆释放后使用(Use-After-Free)

评分:CVSS 4.0 = 9.2

影响版本:PHP 5.4.0 – 8.2.34 / 8.3.35 / 8.4.26 / 8.5.11

修复版本:8.2.35 / 8.3.36 / 8.4.27 / 8.5.12


根因在 ext/session/session.c,与 multipart 上传进度特性相关。


该特性会把上传进度数据缓存进 session 的 HashTable,供进度查询使用。问题在于缓存进去的是 zval* 裸指针:


session.c:3292  →  post_bytes_processed

session.c:3318  →  current_file_bytes_processed


而进度更新存在节流:两次 FILE_DATA 事件间隔超过 min_freq(默认 1 秒)时,第二次事件会重新初始化 session:


php_session_rfc1867_update()

    → php_session_initialize()

        → php_session_track_init()   ← 旧 HashTable 在此释放


旧表释放了,缓存的裸指针却仍然指向那块内存。随后 session.c:3331-3332 通过失效指针写入字节数,构成一次 UAF 写。


这类缺陷的隐蔽性来自它的"安静":不崩溃、不报错、请求返回 200。它写坏的东西在应用层看起来一切正常,因此既难以被发现,也难以用"服务还活着"来反证安全。




0x02 复现环境


宿主系统:Windows 11,版本 10.0.26200

处理器:Ryzen 7 8745HS,8C/16T

内存:16 GB

Linux 层:WSL2,版本 3.0.3.0

发行版:Ubuntu 24.04.3 LTS

容器:Docker 29.9.2

靶场组件:nginx 1.22.1 + php-fpm 8.4.4-fpm


轻薄本完全够用,单容器运行时占用 200-300 MB,CPU 基本空转。




0x03 UAF 路径验证


使用官方公开仓库中的隔离靶场:


git clone --depth 1 b4eK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6K6P5i4u0W2P5o6p5H3x3e0y4Q4x3V1k6H3K9s2l9J5M7$3S2W2L8r3H3`.

cd php2shell/lab-uaf

docker compose up -d --build


确认环境处于漏洞状态:


curl -s 571K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8U0p5J5y4#2)9J5k6e0m8Q4x3X3f1H3i4K6u0W2x3g2)9K6b7e0p5&6x3U0j5^5i4K6u0r3


返回内容:


PHP 8.4.4 (fpm-fcgi)

session.upload_progress.enabled = 1

session.upload_progress.min_freq = 1

session.upload_progress.name = PHP_SESSION_UPLOAD_PROGRESS


运行官方验证脚本:


python3 scripts/php2shell_poc.py --monitor --verbose --rounds 5


输出:


── Round 1/5 ──

    [>] Phase 1 sent: 5591 bytes

    [>] Phase 2 sent: 10267 bytes

    SID:     fb719e2894ed430b9ff05fabb60dc294

    Status:  200 OK

    Armed:   YES — bytes_processed = 5331

    ► Cached zval* pointers were written through (stale path reached)


汇总:


Rounds:          5

Pointers armed:  5/5

Worker crashes:  0/5


注意最后一行。0 崩溃不代表漏洞不存在,恰恰相反——静默写入才是有意义的信号:写入落在了可控位置,只是那块内存尚未被重新分配。一旦堆布局对上,同一个动作就会从"静默"变成"可控"。




0x04 为什么我最后拿到了 shell:两条路径的辨析


官方 README 在 Limitations 中明确写着:


"This PoC proves the vulnerable code path is reachable. It does not achieve a crash or code execution."


也就是说,官方 PoC 只证明代码路径可达,没有实现代码执行。


要把 UAF 变成 RCE,还需要堆喷精确回收 ZendMM 的 320 字节 slab bin,并适配具体构建版本的符号偏移。这属于另一个量级的工程工作,其中涉及的时序与布局问题,公开资料里并没有完整实现。


那我拿到的 uid=33(www-data) 从哪来?走的是另一条与 UAF 无关的路径:


session 投毒(把 PHP 代码写进 session 存储文件)

    ↓

php-fpm 9000 端口从外部可达

    ↓

FastCGI 直连,注入 PHP_VALUE=auto_prepend_file=<被投毒的 session 文件>

    ↓

index.php 执行前 include 该文件,导致代码执行


两条路径的差异如下。


关于是否依赖本次漏洞:左边(UAF 到 RCE)依赖,右边(session 投毒 + FastCGI)不依赖。

关于额外前提:左边没有额外前提,右边需要 fpm 端口可达。

关于难度:左边极高,需要堆布局加版本适配;右边低。


[招生]科锐逆向工程师培训(2026年11月13日实地,远程教学同时开班, 第57期)

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