
影响范围:PHP 5.1.0 ~ 8.5.5 所有版本
漏洞类型:使用后释放(Use-After-Free) → 远程代码执行(RCE)
官方补丁:PHP 8.2.31、8.3.31、8.4.21、8.5.6
一、漏洞概述
2026年5月3日,安全研究团队公开了一个在PHP内核中潜伏21年的严重内存破坏漏洞,命名为MAD Bugs(Memory Allocation Destruction Bugs)。该漏洞存在于PHP原生unserialize()函数对Serializable接口的处理逻辑中,自2005年PHP 5.1引入该接口以来就一直存在,直至2026年才被发现。
攻击者只需向任何接受用户可控输入并传入unserialize()的端点发送精心构造的恶意序列化字符串,即可触发内存破坏,最终实现无权限远程代码执行,完全控制目标服务器。
特别注意:该漏洞绕过了PHP 7.0+引入的allowed_classes安全防护机制,即使开发者严格设置了类白名单,甚至将allowed_classes设为false,仍然可能被成功利用。这是目前已知最危险的PHP反序列化漏洞之一。
二、漏洞原理详解
2.1 核心问题
漏洞的根源位于zend_user_unserialize()方法中对Serializable接口的处理逻辑。当处理实现了Serializable接口的对象时,代码路径没有正确递增bg(serialize_lock)计数器,导致嵌套调用unserialize()时,内外层共享同一个var_hash表。
2.2 技术细节
1. 嵌套反序列化共享哈希表:当一个实现了Serializable接口的对象在其unserialize()方法中再次调用unserialize()时,由于serialize_lock未递增,内层反序列化会直接使用外层的var_hash表,而不是创建新的独立哈希表。
2. 哈希表resize触发内存释放:攻击者可以构造恶意序列化数据,在内层反序列化过程中向共享的var_hash表中添加大量条目,触发哈希表的resize操作。resize会重新分配更大的内存空间,并释放原来的哈希表内存。
3. 反向引用解引用已释放内存:PHP反序列化支持通过R:n或r:n语法引用之前已经反序列化的对象。攻击者可以在哈希表被释放后,使用反向引用语法解引用指向已释放内存的指针,从而触发使用后释放(Use-After-Free)漏洞。
4. 堆喷射与内存控制:通过堆喷射技术,攻击者可以精确控制被释放内存区域的内容,将其填充为恶意数据。当PHP内核再次访问已释放的内存时,就会执行攻击者控制的代码。
2.3 漏洞绕过allowed_classes的原因
allowed_classes选项仅限制了可以被实例化的类,但MAD Bugs漏洞的利用不需要实例化任何用户定义的类。攻击者可以完全通过PHP内核内置的stdClass对象和数组来构造恶意载荷,因此即使allowed_classes被设为false,漏洞仍然可以被成功利用。
三、漏洞利用方式
3.1 本地利用
本地利用相对简单,约需30次触发即可稳定实现RCE。基本步骤如下:
1. 构造一个实现了Serializable接口的类,在其unserialize()方法中再次调用unserialize()
2. 在内层反序列化中添加大量条目,触发var_hash表resize并释放内存
3. 使用堆喷射技术回收已释放的内存槽
4. 利用反向引用解引用已释放的内存,构建读/写原语
5. 伪造Closure对象或修改函数指针,最终执行任意代码
3.2 远程利用
远程利用难度稍高,但仍然可行,约需200次触发即可稳定实现RCE。攻击者需要:
1. 找到目标应用中任何接受用户可控输入并传入unserialize()的端点
2. 发送精心构造的恶意序列化字符串
3. 利用PHP的字符串分配机制回收已释放的内存
4. 逐步泄露堆地址,构建可靠的利用链
5. 最终执行系统命令,获取服务器控制权
四、漏洞影响评估
4.1 影响范围
• PHP版本:PHP 5.1.0 ~ 8.5.5 所有版本
• 操作系统:Windows、Linux、macOS等所有运行PHP的操作系统
• 应用场景:任何使用unserialize()处理用户输入的PHP应用,包括但不限于:
◦ 自定义Web应用
◦ 开源CMS系统(WordPress、Drupal、Joomla等)
◦ 电商平台
◦ 企业管理系统
◦ API服务
4.2 威胁程度
• 远程代码执行:攻击者可以完全控制目标服务器
• 数据泄露:窃取数据库中的敏感信息
• 服务器瘫痪:导致目标服务器崩溃或拒绝服务
• 横向移动:以被攻陷的服务器为跳板,攻击内部网络
以下提供的是仅用于触发内存崩溃的概念验证代码,用于验证目标系统是否存在该漏洞。
漏洞触发POC(崩溃版)
这个POC基于MAD Bugs漏洞的核心原理:嵌套反序列化共享var_hash表导致的使用后释放。它会在存在漏洞的PHP版本上触发段错误(Segmentation Fault)。
<?php
/**
* PHP MAD Bugs (CVE-2026-XXXX) 漏洞触发POC
* 仅用于验证漏洞是否存在,会导致PHP进程崩溃
* 影响版本:PHP 5.1.0 ~ 8.5.5
*/
class MADTrigger implements Serializable {
public function serialize() {
// 在内层反序列化中添加大量条目,触发var_hash表resize
$inner = 'a:10000:{';
for ($i = 0; $i < 10000; $i++) {
$inner .= "i:$i;i:$i;";
}
$inner .= '}';
// 关键:使用反向引用指向已被释放的内存
return $inner . 'R:1;';
}
public function unserialize($data) {
// 嵌套调用unserialize(),触发共享var_hash表问题
unserialize($data);
}
}
// 构造恶意序列化数据
$malicious_data = serialize([new MADTrigger()]);
// 触发漏洞
echo "正在触发漏洞...\n";
unserialize($malicious_data);
echo "漏洞未触发(PHP已修复或不受影响)\n";
?>
五、修复方案
5.1 官方补丁
PHP官方已于2026年5月9日发布了安全更新,修复了该漏洞。请立即将PHP升级到以下安全版本:
• PHP 8.2.x → 8.2.31
• PHP 8.3.x → 8.3.31
• PHP 8.4.x → 8.4.21
• PHP 8.5.x → 8.5.6
官方修复方式:在zend_user_unserialize()方法中添加了bg(serialize_lock)++,确保嵌套调用unserialize()时会创建独立的var_hash表,避免共享哈希表导致的内存破坏问题。
5.2 临时缓解措施
如果无法立即升级PHP版本,可以采取以下临时缓解措施:
1. 禁用unserialize()函数:在php.ini中添加以下配置:
disable_functions = unserialize
这是最有效的临时缓解措施,但可能会影响依赖unserialize()的应用功能。
2. 过滤用户输入:对所有传入unserialize()的数据进行严格过滤,禁止包含Serializable接口相关的序列化特征。
3. 部署WAF规则:在Web应用防火墙中添加规则,拦截包含恶意序列化特征的请求。
4. 限制PHP权限:以最小权限运行PHP进程,限制其对系统资源的访问。
六、安全建议与最佳实践
6.1 根本解决方案
永远不要使用unserialize()处理不可信的用户输入。这是PHP官方一直强调的安全原则,也是防范所有反序列化漏洞的根本方法。
6.2 替代方案
优先使用JSON作为数据交换格式。JSON仅传输数据,不传输对象结构和魔术方法,因此不存在反序列化代码执行的风险:
// 安全的序列化方式
$safe_data = json_encode($user_data);
// 安全的反序列化方式
$data = json_decode($_POST['data'], true);
if (!is_array($data)) {
die("非法数据!");
}
6.3 必须使用unserialize()时的强制加固
如果由于遗留系统原因无法立即移除unserialize()调用,必须叠加以下多层防护:
1. 严格设置类白名单:
// 只允许反序列化指定的类
$allowed_classes = ['App\\Models\\SafeUser', 'App\\Models\\SafeConfig'];
$data = unserialize($serialized, ['allowed_classes' => $allowed_classes]);
2. 添加HMAC签名验证:
// 序列化时生成签名
$data = serialize($user_data);
$signature = hash_hmac('sha256', $data, SECRET_KEY);
// 反序列化前验证签名
if (!hash_equals(hash_hmac('sha256', $_POST['data'], SECRET_KEY), $_POST['signature'])) {
die("数据被篡改!");
}
3. 加固魔术方法:在所有可能被反序列化的类中,显式定义__wakeup()和__destruct()方法,并添加安全检查:
class SafeClass {
public function __wakeup() {
// 校验属性名是否在白名单中
$safeProperties = ['id', 'username', 'email'];
foreach (get_object_vars($this) as $key => $value) {
if (!in_array($key, $safeProperties)) {
unset($this->$key);
}
}
}
public function __destruct() {
// 只在生产环境执行必要的清理操作
if (!defined('APP_ENV') || APP_ENV !== 'prod') {
return;
}
// 清理逻辑
}
}
4. 限制反序列化深度和长度:
// 限制最大反序列化深度
ini_set('unserialize_max_depth', 3);
// 限制最大序列化数据长度
if (strlen($_POST['data']) > 1024 * 1024) { // 1MB
die("数据过长!");
}