反序列化
- 反序列化漏洞通常出现在处理用户输入数据时,尤其是当程序将序列化的数据?还原成对象时,没有进行充分的验证,导致恶意代码执行
- 反序列化漏洞是CTF中Web和二进制方向的常见考点,也是实际渗透中的高危漏洞
- 序列化(Serialize):将对象转换为可存储/传输的字符串(如JSON、PHP序列化格式)
- 反序列化(Unserialize):将字符串还原为对象(关键漏洞点:若反序列化过程可控,可能触发恶意代码)
- 本质上反序列化是没有危害的。但是如果用户对数据可控那就可以利用反序列化构造payload攻击
常见语言中的反序列化函数
| 语言 |
序列化函数 |
反序列化函数 |
| PHP |
serialize() |
unserialize() |
| Python |
pickle.dumps |
pickle.loads() |
| Java |
ObjectOutputStream |
ObjectInputStream |
漏洞入门
触发原理
- 魔术方法自动调用:PHP在反序列化时会自动执行特定方法(如
__wakeup(), __destruct())
示例:
class VulnClass {
public $cmd = 'whoami';
public function __destruct() {
system($this->cmd); // 危险函数!
}
}
// 反序列化用户可控数据
$data = $_GET['data'];
unserialize($data);
# 攻击思路:构造序列化数据,使$cmd为恶意命令。
## 生成Payload:本地生成恶意对象
$obj = new VulnClass():
$obj->cmd = 'ls /';
echo serialize($obj);
__wakeup()
- 在反序列化时自动调用的,通常用于重新建立资源或初始化对象
- 触发时机:
- 在unserialize()反序列化完成后立即自动调用
- 用于对象反序列化后的初始化操作(如重建数据库连接)
- 漏洞利用价值:
- 初始化重置:可能覆盖攻击者控制的属性,阻碍漏洞利用
- 示例:
- 若直接反序列化$path为恶意值,
__wakeup()会将其重置为安全值。
- 绕过
__wakeup()的限制
- CVE-2016-7124漏洞:当序列化字符串中对象属性数量大于实际值时,
__wakeup()不会执行。
- 正常序列化:O:7:"MyClass":1:
- 修改属性数量绕过:O:7:"MyClass":2:
class ResetDemo {
public $path = "/tmp/default.log";
public function __wakeup() {
$this->path = "/tmp/safe.log"; // 重置路径
}
public function __destruct() {
file_put_contents($this->path, "Log");
}
}
__destruct(
- 在对象销毁时调用,比如脚本执行结束或对象被显式销毁的时候
- 触发时机:
- 当对象被销毁时自动调用(如脚本执行结束、unset()被调用、对象引用计数归零)
- 反序列化场景:反序列化生成的临时对象在脚本执行完毕后销毁,触发
__destruct()
- 常见用途:执行最终操作
- CTF利用技巧
- POP链起点:通过
__destruct()触发后续链式调用(如调用其他对象的方法)。
- 稳定性:无论反序列化是否成功,只要对象被创建,脚本结束时必然触发
- 示例代码:
- 攻击方式:反序列化一个Exploit对象,控制$cmd为恶意命令。
class Exploit {
public $cmd = "echo 'Safe'";
public function __destruct() {
system($this->cmd); // 危险操作!
}
}
核心:POP链
- Property-Oriented Programming(面向属性编程):通过链式调用多个类的魔术方法,最终执行危险操作
- 适用场景:当目标代码中没有直接执行命令的类,但存在多个可串联利用的类
- 构造POP链步骤
- 代码审计:寻找所有类的魔术方法(
__wakeup, __toString, __call等)
- 链式调用:通过对象的属性关联多个类,形成调用链。
- 触发点:找到一个反序列化入口点(如
unserialize($_COOKIE['data']))
- 经典案例:
class A {
public $b;
public function __destruct() {
$this->b->action(); // 调用B类的action方法
}
}
class B {
public $c;
public function action() {
echo $this->c; // 触发C类的__toString
}
}
class C {
public function __toString() {
system($this->cmd); // 最终执行命令
}
}
// 构造链:A->B->C
$a = new A();
$a->b = new B();
$a->b->c = new C();
$a->b->c->cmd = 'id';
echo serialize($a);
工具推荐
- PHPGGC:自动生成PHP反序列化链(GitHub开源)
- PHPGGC 是一个 unserialize() 有效负载库,以及从命令行或编程方式生成它们的工具
- 这个工具是针对于那些框架、组件来生成payload的,而不是原生类的,比如说很多CTF的题目,别人自己出的那种不涉及框架的代码,用这个工具是生成不出来的
- 下载和使用
- 使用:
./phpggc -l # 查看可用链
./phpggc Monolog/RCE1 system 'id' -p phar -o payload.phar
- SerializationDumper:Java反序列化Payload分析工具
HP 序列化/反序列化的容错性
- PHP的序列化引擎在解析字符串时具有一定的容错性。当遇到数字前的正号(+)时,PHP会:
- 忽略符号(+或-),仅保留数字部分
- 将O:+4:"Demo"视为O:4:"Demo"(即类名为Demo,长度为4,符合实际)。
- 因此,虽然序列化字符串中多了一个+,但PHP仍能正确解析对象的结构。
类中存在私有属性
- 若类中存在私有属性,则序列化的时候正确的序列化字符串应该包含空字节,直接php输出会出现
NULL,可以用空格替代,记得相应增加字符串长度的值,因为NULL未被计入
- 也可以直接在php中进行操作,直接转换掉,如进行base编码