PHP 反序列化:CVE-2016-7124 绕过 __wakeup

记录 PHP 反序列化题中私有属性赋值、__wakeup 绕过和 CVE-2016-7124 的分析过程。

  • #CTF
  • #PHP
  • #反序列化
  • #CVE-2016-7124

一、前置工作

使用 dirsearch 扫描发现源码,进一步发现代码中存在 __wakeup() 和 __destruct() 魔法方法。

需要注意:魔法方法本身不一定就是漏洞。真正需要关注的是,是否存在外部可控数据进入 unserialize(),以及这些魔法方法中是否包含敏感操作或可利用的调用链。

二、分析难点

1. __wakeup() 会覆盖属性值

只要对象被正常反序列化,PHP 就会在对象恢复过程中调用 __wakeup()。题目中的 __wakeup() 会把 $username 强制重置为 guest,因此即使序列化字符串中把用户名改成 admin,正常反序列化后也会被覆盖。

2. 私有属性赋值失败

私有属性在 PHP 序列化结果中的键名不是普通的属性名。私有属性需要使用下面的格式:

\0类名\0属性名

例如,类名为 Name、属性名为 username 时,实际键名应为:

\0Name\0username

其中两个 \0 是空字节,不是普通的字符 \ 和 0。如果直接写成 username,PHP 找不到对应的私有属性,赋值就会失败,属性会继续保持初始值 nonono。

三、CVE-2016-7124 绕过 __wakeup()

CVE-2016-7124 影响部分旧版本 PHP,常见范围为:

  • PHP 5.6.25 及以下版本
  • PHP 7.0.10 及以下版本

该问题与反序列化字符串中声明的属性数量有关。实际利用前仍需要确认题目环境确实运行在受影响版本,并且存在可控的反序列化入口。

1. 修改属性数量

假设原始序列化对象的开头为:

O:4:"Name":2:{

其中 2 表示对象声明了两个属性。将声明数量改为大于实际属性数量,例如:

O:4:"Name":3:{

在受影响的旧版本 PHP 中,声明数量与实际属性数量不一致可能导致反序列化流程异常,从而绕过 __wakeup() 的调用,保住序列化字符串中写入的属性值。

这不是通用的 PHP 行为,必须结合具体版本验证;新版本 PHP 不应默认认为仍然存在该绕过效果。

2. 处理私有属性

原始数据可以抽象表示为:

O:4:"Name":2:{s:14:"\0Name\0username";s:5:"admin";s:14:"\0Name\0password";i:100;}

如果序列化字符串通过 URL 参数或表单传输,空字节通常需要 URL 编码为 %00:

O:4:"Name":2:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";i:100;}

最终需要把属性数量改大,同时严格使用 %00类名%00属性名 的格式,才能正确定位私有属性。实际发送时,是否需要手动编码取决于请求所在位置和服务端的 URL 解码流程。

四、完整思路整理

  1. 通过源码或题目行为确认数据会进入 unserialize()。
  2. 阅读 __wakeup(),确认它会覆盖目标属性。
  3. 判断目标属性是否为私有属性,并按 \0类名\0属性名 构造键名。
  4. 确认题目运行的 PHP 版本是否处于 CVE-2016-7124 影响范围。
  5. 将序列化对象声明的属性数量修改为大于实际数量,以尝试绕过 __wakeup()。
  6. 根据请求传输方式,将空字节编码为 %00,再提交构造后的数据。
  7. 观察属性是否成功保留,以及后续业务逻辑是否发生变化。

五、容易忽略的问题

  • __destruct() 不等于漏洞。它只有在包含敏感操作、危险调用或可利用对象链时,才可能成为利用链的一环。
  • CVE-2016-7124 是旧版本 PHP 的特定问题,不能把它当作所有 PHP 反序列化场景的通用绕过方法。
  • 私有属性名的长度要按包含空字节的字节数计算。\0Name\0username 的长度为 14,而不是只计算可见字符。
  • %00 是否会被还原为空字节,取决于参数解析和 URL 解码流程;直接把 %00 放进已经完成解码的上下文可能不会得到预期结果。
  • 修改对象属性数量时,要保持序列化格式的其他部分完整,否则可能只是得到一个损坏的序列化字符串。

六、总结

这道题的关键不只是找到 __wakeup() 和 __destruct(),而是把三个问题串起来:反序列化入口、私有属性的序列化格式,以及旧版本 PHP 对属性数量异常的处理。

通过将序列化字符串中的属性数量改为大于实际数量,并使用 %00类名%00属性名 处理私有属性,可以在受影响的 PHP 版本中尝试绕过 __wakeup(),保住已经写入的属性值。

使用工具

dirsearch

用于扫描 Web 路径,发现题目环境中可能暴露的源码或其他可访问文件。