文章总结: PHPunserialize()函数存在潜伏21年的Use-After-Free漏洞(MADBugs),影响5.1.0至8.5.5版本,攻击者可构造恶意序列化数据实现远程代码执行并绕过allowed_classes防护。建议立即升级至官方补丁版本,或禁用unserialize()函数、采用JSON替代方案并严格过滤用户输入。 综合评分: 92 文章分类: 漏洞分析,WEB安全,漏洞预警,解决方案
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 技术细节
-
嵌套反序列化共享哈希表:当一个实现了Serializable接口的对象在其unserialize()方法中再次调用unserialize()时,由于serialize_lock未递增,内层反序列化会直接使用外层的var_hash表,而不是创建新的独立哈希表。
-
哈希表resize触发内存释放:攻击者可以构造恶意序列化数据,在内层反序列化过程中向共享的var_hash表中添加大量条目,触发哈希表的resize操作。resize会重新分配更大的内存空间,并释放原来的哈希表内存。
-
反向引用解引用已释放内存:PHP反序列化支持通过R:n或r:n语法引用之前已经反序列化的对象。攻击者可以在哈希表被释放后,使用反向引用语法解引用指向已释放内存的指针,从而触发使用后释放(Use-After-Free)漏洞。
-
堆喷射与内存控制:通过堆喷射技术,攻击者可以精确控制被释放内存区域的内容,将其填充为恶意数据。当PHP内核再次访问已释放的内存时,就会执行攻击者控制的代码。
2.3 漏洞绕过allowed_classes的原因
allowed_classes选项仅限制了可以被实例化的类,但MAD Bugs漏洞的利用不需要实例化任何用户定义的类。攻击者可以完全通过PHP内核内置的stdClass对象和数组来构造恶意载荷,因此即使allowed_classes被设为false,漏洞仍然可以被成功利用。
三、漏洞利用方式
3.1 本地利用
本地利用相对简单,约需30次触发即可稳定实现RCE。基本步骤如下:
-
构造一个实现了Serializable接口的类,在其unserialize()方法中再次调用unserialize()
-
在内层反序列化中添加大量条目,触发var_hash表resize并释放内存
-
使用堆喷射技术回收已释放的内存槽
-
利用反向引用解引用已释放的内存,构建读/写原语
-
伪造Closure对象或修改函数指针,最终执行任意代码
3.2 远程利用
远程利用难度稍高,但仍然可行,约需200次触发即可稳定实现RCE。攻击者需要:
-
找到目标应用中任何接受用户可控输入并传入unserialize()的端点
-
发送精心构造的恶意序列化字符串
-
利用PHP的字符串分配机制回收已释放的内存
-
逐步泄露堆地址,构建可靠的利用链
-
最终执行系统命令,获取服务器控制权
四、漏洞影响评估
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版本,可以采取以下临时缓解措施:
-
禁用unserialize()函数:在php.ini中添加以下配置: disable_functions = unserialize 这是最有效的临时缓解措施,但可能会影响依赖unserialize()的应用功能。
-
过滤用户输入:对所有传入unserialize()的数据进行严格过滤,禁止包含Serializable接口相关的序列化特征。
-
部署WAF规则:在Web应用防火墙中添加规则,拦截包含恶意序列化特征的请求。
-
限制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()调用,必须叠加以下多层防护:
-
严格设置类白名单: // 只允许反序列化指定的类 $allowed_classes = [‘App\Models\SafeUser’, ‘App\Models\SafeConfig’]; $data = unserialize($serialized, [‘allowed_classes’ => $allowed_classes]);
-
添加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(“数据被篡改!”); }
-
加固魔术方法:在所有可能被反序列化的类中,显式定义__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; } // 清理逻辑 } }
-
限制反序列化深度和长度: // 限制最大反序列化深度 ini_set(‘unserialize_max_depth’, 3);
// 限制最大序列化数据长度 if (strlen($_POST[‘data’]) > 1024 * 1024) { // 1MB die(“数据过长!”); }
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:飓风网络安全 《PHP unserialize() 潜伏21年致命Use-After-Free漏洞》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论