php8 首个 bypass disable function漏洞
字数 3167
更新时间 2026-05-08 07:29:22
PHP 8 首个 Bypass Disable Function 漏洞分析与利用教学文档
1. 概述
本文档针对先知社区(xz.aliyun.com)发布的一篇关于“PHP8 首个 bypass disable function漏洞”的技术文章进行系统性梳理与教学化总结。该漏洞是首个在 PHP 8 环境下可达成绕过 disable_functions 限制的利用技巧,并已被武器化为蚁剑(AntSword)插件。
2. 漏洞基本信息
- 漏洞性质:一个潜伏在 PHP 核心代码中长达 20 年(自 PHP 5.1 引入 Serializable 接口起至 PHP 8.5)的高危 Use-After-Free(UAF)漏洞。
- 影响版本:目前公开的利用工具(蚁剑插件)支持 PHP 8.4 和 8.5 两个小版本。其他版本的 PHP 8 可能存在此漏洞,但因内存偏移不同,需进行二进制适配。
- 漏洞价值:可从“内存破坏”到“任意代码执行”,特别是在目标服务器开启
disable_functions限制(禁用system、shell_exec等函数)的情况下,实现远程命令执行(RCE)。
3. 漏洞成因深度分析
3.1 核心触发点
漏洞位于 PHP 反序列化函数 unserialize() 在处理实现了 Serializable 接口的类时。
3.2 技术细节
-
var_hash的作用:- PHP 反序列化底层引擎维护一个名为
var_hash的哈希表,用于记录已解析过的变量,以支持处理R:N;(引用)语法时能找回对应对象。
- PHP 反序列化底层引擎维护一个名为
-
漏洞代码位置:
- 位于
Zend/zend_interfaces.c文件中的zend_user_unserialize()函数。
- 位于
-
漏洞触发逻辑:
- 当
zend_user_unserialize()调用一个类自定义的unserialize()方法时,没有递增BG(serialize_lock)这个锁变量。 - 如果在自定义的
unserialize()方法内部再次递归调用了unserialize(),则内部的反序列化过程会继承外层的var_hash。 - 如果内部反序列化注册了一些对象,随后通过某些操作(例如,向对象动态添加属性,导致底层 Hash Table 容量超过初始值 8 而触发扩容),旧的内存块(
arData)会被efree释放。 - 然而,外层的
var_hash仍然保留着指向这些已被释放内存的指针(即悬垂指针,Dangling Pointers)。 - 当外层数据中再出现一个
R:N;引用这些“槽位”时,经典的 Use-After-Free(UAF)内存破坏条件即被触发。
- 当
4. 反序列化利用链构建
4.1 最小化触发条件
要触发此漏洞,只需一个基础的反序列化操作:
unserialize($data)->x = 0;
4.2 触发步骤分解
- 构造恶意序列化字符串
$data。 - 内部 Payload 结构:
- Payload 反序列化出一个带有 8 个属性 的
stdClass对象。这恰好填满 PHP 底层 Hash Table 的初始容量(nTableSize = 8)。
- Payload 反序列化出一个带有 8 个属性 的
- 关键触发点:
->x = 0赋值操作。- 这是尝试写入的第 9 个属性,强制触发底层 Hash Table 的容量倍增(从 8 扩容到 16)。
- 内存释放:
- 扩容操作导致原本存储 8 个属性的、大小为 288 字节的
arData内存块被efree回收。
- 扩容操作导致原本存储 8 个属性的、大小为 288 字节的
- UAF 达成:
- 由于前述漏洞,
var_hash中仍保留着指向这块已释放内存的引用,成功制造出 Use-After-Free 场景。
- 由于前述漏洞,
4.3 漏洞复现环境搭建
在目标服务器上放置以下 PHP 脚本作为攻击端点:
<?php
error_reporting(0);
class CachedData implements Serializable {
public function serialize(): string { return ''; }
public function unserialize(string $data): void {
unserialize($data)->x = 0; // 漏洞触发点
}
}
echo serialize(@unserialize($_REQUEST['cook']));
5. 从 UAF 到 Bypass Disable Functions 的完整利用链
利用的核心目标是将内存读写能力转化为在 disable_functions 开启情况下的命令执行能力。整个利用流程(PoC流程)可概括为以下步骤:
-
堆地址泄露:
- 利用 UAF 的写入贯穿特性,泄露出堆内存的基址,为后续内存操作奠定基础。
-
对象喷射:
- 在堆内存中喷射大量精心构造的、伪造的闭包(Closure)对象,目的是占据被释放的内存区域,并控制其内容。
-
内存漫游与关键结构定位:
- 构造一个巨大的(例如 64MB)字符串来扫描进程内存空间。
- 目标是定位 PHP 的核心全局结构:
- Executor Globals (EG):PHP 执行器全局变量表,包含大量运行时信息。
function_table:PHP 内部注册的所有函数的哈希表。即使disable_functions在用户层禁用了函数,这些函数的底层 C 实现指针(zend_function)仍注册在此表中。
-
定位并劫持
system函数:- 从定位到的
function_table中,解析并找到zif_system(即system函数的底层 C 实现)的内存地址。 - 关键点:此操作完全绕过了 PHP 用户态的
disable_functions限制,因为该限制只拦截了用户层对函数名的调用,并未从底层删除函数指针。
- 从定位到的
-
类型混淆与函数指针劫持:
- 通过操纵在步骤2中喷射的伪造闭包对象,将其内部的函数执行指针修改为步骤4中找到的
zif_system地址。
- 通过操纵在步骤2中喷射的伪造闭包对象,将其内部的函数执行指针修改为步骤4中找到的
-
最终 RCE 触发:
- 调用被篡改的闭包对象。由于它的函数指针已指向
system,因此调用该闭包等同于调用system()函数,从而成功执行任意系统命令,实现远程代码执行。
- 调用被篡改的闭包对象。由于它的函数指针已指向
6. 自动化利用工具
- 工具名称:蚁剑(AntSword)插件
- 项目地址:https://github.com/ez-lbz/php8-UAF
- 工具功能:自动化完成上述从内存泄露、定位到最终命令执行的完整利用链。
- 当前支持:已验证在 PHP 8.4 和 8.5 环境下有效。
7. 总结与影响
- 漏洞渊源:据原始研究(MAD Bugs 博客)透露,此漏洞是通过向 AI(如 Claude)投喂历史上 PHP 的 UAF 漏洞案例,由安全研究员与 AI 协同分析发现的。这展示了AI在漏洞挖掘中的潜在应用。
- 历史地位:这是公开的首个在 PHP 8 系列中可实现绕过
disable_functions的利用方法,具有重要的实战价值。 - 后续影响:预计此漏洞及利用技术将在后续的 CTF 竞赛、红队评估及安全研究中频繁出现。
8. 防御建议(基于模型知识的补充)
文档中未详述防御措施,但基于我所掌握的知识,针对此类漏洞的防护可考虑以下层面:
- 及时更新:密切关注 PHP 官方发布的安全更新,并及时将 PHP 升级到已修复此漏洞的版本。
- 输入过滤:对用户输入的、传递给
unserialize()函数的数据进行严格校验,避免反序列化不可信的数据。 - 禁用危险函数:虽然此漏洞可绕过
disable_functions,但保持该设置仍能防御大多数普通 webshell。可考虑结合其他安全机制,如禁用unserialize()函数本身(通过disable_functions或配置)。 - 深度防御:
- 使用 Suhosin 等 PHP 安全加固扩展。
- 部署 Web 应用防火墙(WAF),配置规则拦截可疑的反序列化 Payload。
- 在容器或系统层面进行权限最小化控制,限制 PHP 进程的系统调用能力。
相似文章
相似文章