PHPunserialize()潜伏21年致命Use-After-Free漏洞

admin 2026-05-12 04:52:30 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: PHPunserialize()函数存在潜伏21年的Use-After-Free漏洞(MADBugs),影响5.1.0至8.5.5版本,攻击者可构造恶意序列化数据实现远程代码执行并绕过allowed_classes防护。建议立即升级至官方补丁版本,或禁用unserialize()函数、采用JSON替代方案并严格过滤用户输入。 综合评分: 92 文章分类: 漏洞分析,WEB安全,漏洞预警,解决方案


cover_image

PHP unserialize() 潜伏21年致命Use-After-Free漏洞

飓风网络安全

2026年5月11日 23:54 北京

在小说阅读器读本章

去阅读

影响范围: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(“数据被篡改!”); }

  1. 加固魔术方法:在所有可能被反序列化的类中,显式定义__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; } // 清理逻辑 } }

  2. 限制反序列化深度和长度: // 限制最大反序列化深度 ini_set(‘unserialize_max_depth’, 3);

// 限制最大序列化数据长度 if (strlen($_POST[‘data’]) > 1024 * 1024) { // 1MB die(“数据过长!”); }


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:飓风网络安全 《PHP unserialize() 潜伏21年致命Use-After-Free漏洞》

评论:0   参与:  0