反序列化漏洞
一、序列化与反序列化
1.1 概念
序列化(Serialization): 将对象的状态信息转换为可以存储或传输的形式的过程,一般将对象转换为字节流。序列化时,对象的当前状态被写入到临时或持久性存储区(文件、内存、数据库等)。
反序列化(Deserialization): 从序列化的表示形式中提取数据,即把有序字节流恢复为对象的过程。
反序列化攻击: 攻击者控制了序列化后的数据,将有害数据传递到应用程序代码中,发动针对应用程序的攻击。
二、PHP序列化与反序列化
2.1 介绍
在PHP应用中,序列化和反序列化一般用做缓存,比如session缓存,cookie等。
常见的序列化格式:
- 二进制格式
- 字节数组
- json字符串
- xml字符串
2.2 实现方式
PHP通过string serialize ( mixed $value ) 和mixed unserialize ( string $str ) 两个函数实现序列化和反序列化
2.2.1 数组序列化与反序列化
<?php
echo '序列化:';
$human = array('name'=>'zhangshan','age'=>20);
$str = serialize($human);
echo $str;
echo "反序列化:";
$human_1 = unserialize($str);
echo var_dump($human_1);
?>
序列化:a:2:{s:4:"name";s:9:"zhangshan";s:3:"age";i:20;}
反序列化:array(2) { ["name"]=> string(9) "zhangshan" ["age"]=> string(2) "20" }
- 序列化格式:
- a表示array,2表示连个元素;
- s:4:“name” 表示第一个元素名称为字符串,长度为4,值为name;
- s:9:"zhangshan"表示第一个元素的值为字符串,长度为9,值为zhangshan;
- s:3:"age"表示第二个元素名称为字符串,长度为3,值为age;
- i:20 表示第二个元素值为int型,值为20;
2.2.2 对象序列化与反序列化
<?php
class hello{
public $var1;
protected $var2;
private $var3;
function __construct($var1,$var2,$var3){
$this->var1=$var1;
$this->var2=$var2;
$this->var3=$var3;
}
}
echo '序列化:';
$syh=new hello("xiaoming","hello",99);
$str = serialize($syh);
$str2 = urlencode($str); # URL编码
echo $str;
echo "<br>";
echo $str2;
echo "反序列化:";
$syh1 = unserialize($str);
echo "<br>";
echo var_dump($syh1);
?>
序列化:O:5:"hello":3:{s:4:"var1";s:8:"xiaoming";s:7:"*var2";s:5:"hello";s:11:"hellovar3";i:99;}
O%3A5%3A%22hello%22%3A3%3A%7Bs%3A4%3A%22var1%22%3Bs%3A8%3A%22xiaoming%22%3Bs%3A7%3A%22%00%2A%00var2%22%3Bs%3A5%3A%22hello%22%3Bs%3A11%3A%22%00hello%00var3%22%3Bi%3A99%3B%7D反序列化:
object(hello)#2 (3) { ["var1"]=> string(8) "xiaoming" ["var2":protected]=> string(5) "hello" ["var3":"hello":private]=> int(99) }
- 序列化格式:
- O:5:“hello”:3 ,表示类型为Object,对象名长度为5,值为hello,且有3个元素。
- s:4:“var1”,表示第一个元素名字为字符串,长度为4,元素名字为var1。
- s:8:“xiaoming”,表示第一个元素值为字符串,长度为8,值为xiaoming。
- s:7:“*var2”,表示第二个元素名字为字符串,长度为7,元素为protected ,名字为var2。
- s:5:“hello”,表示第二元素值为字符串,长度为5,值为hello。
- s:11:“hellovar3”,表示三个元素名字为字符串,长度为11,元素为private ,名字为var3。
- i:99,表示第三个元素值为整型,值为99。
- public成员变量的名字直接写。public $var1;——序列化:s:4:“var1”;——URL编码:s%3A4%3A%22var1
- protected成员变量名字前面要添加%00*%00。protected $var2;——序列化:s:7:“*var2”——URL编码:
s%3A7%3A%22%00%2A%00var2 - private成员变量名字前面要添加%00类名%00对。 private $var3;——序列化:s:11:“hellovar3”——URL编码:s%3A11%3A%22 %00hello%00var3
- 序列化时,只保存成员变量,不保存方法。
2.3 常用魔术方法
魔术方法 | 介绍 |
---|---|
__construct() | 初始化类的时候,一般对于变量进行赋值 |
__toString() | 当一个对象被当作一个字符串被调用,把类当作字符串使用时触发,返回值需要为字符串,否则将发出一条 E_RECOVERABLE_ERROR 级别的致命错误。 |
__wakeup() | 使用unserialize时触发,反序列化恢复对象之前调用该方法 |
__sleep() | 使用serialize时触发.该函数需要返回以类成员变量名作为元素的数组(该数组里的元素会影响类成员变量是否被序列化。只有出现在该数组元素里的类成员变量才会被序列化 |
__destruct() | 对象被销毁时触发 |
__invoke() | 当脚本尝试将对象调用为函数时触发 |
- 运行情况:
<?php class hello{ public $var1; protected $var2; private $var3; function __construct($var1,$var2,$var3){ $this->var1=$var1; $this->var2=$var2; $this->var3=$var3; echo "__contruct<br/>"; } function __destruct(){ echo "__destruct<br/>"; } function __toString(){ echo "__toString<br/>"; return "object of class A;"; } function __wakeup(){ echo "__wakeup<br/>"; } } echo '序列化开始:<br>'; $syh=new hello("xiaoming","hello",99); echo '序列化结束:<br>'; $str = serialize($syh); $str2 = urlencode($str); echo $str; echo "<br>"; echo $str2; echo "<br>"; echo "反序列化开始:<br>"; $syh1 = unserialize($str); echo "反序列化结束:<br>"; echo var_dump($syh1); echo "<br>"; ?>
序列化开始: __contruct 序列化结束: O:5:"hello":3:{s:4:"var1";s:8:"xiaoming";s:7:"*var2";s:5:"hello";s:11:"hellovar3";i:99;} O%3A5%3A%22hello%22%3A3%3A%7Bs%3A4%3A%22var1%22%3Bs%3A8%3A%22xiaoming%22%3Bs%3A7%3A%22%00%2A%00var2%22%3Bs%3A5%3A%22hello%22%3Bs%3A11%3A%22%00hello%00var3%22%3Bi%3A99%3B%7D 反序列化开始: __wakeup 反序列化结束: object(hello)#2 (3) { ["var1"]=> string(8) "xiaoming" ["var2":protected]=> string(5) "hello" ["var3":"hello":private]=> int(99) } __destruct __destruct
三、利用方式
3.1 __wakeup()绕过漏洞
- 影响版本:
- PHP before 5.6.25
- 7.x before 7.0.10
- 反序列化时,如果表示对象属性个数的值大于真实的属性个数时就会跳过__wakeup( )的执行
- 例如:
<?php class hello{ public $var1=10; function __wakeup(){ echo "__wakeup<br/>"; } function __destruct(){ echo "__destruct<br/>"; } } $syh=new hello(); $str = serialize($syh); echo $str ; # O:5:"hello":1:{s:4:"var1";i:10;} echo "<br>"; unserialize('O:5:"hello":2:{s:4:"var1";i:10;}'); # 这里将 "hello":1->"hello":2 ?>
O:5:"hello":1:{s:4:"var1";i:10;}
3.2 引用绕过
-
例如:
<?php class hello{ public $var1; public $var2; function __destruct(){ $this->ver2 = uniqid(); if($var1 === $ver2){ echo "flag"; }else{ echo "nonono"; } } } $syh=new hello(); $syh->var1 = '' ; $syh->var2 = &$syh->var1 ; ?>
flag
-
$syh->var2 = &$syh->var1 ,指定var2 是var1的引用,所以两个的值完全一样。
四、其他
4.1 序列化字符串中的+号
- 在序列化字符串中的数字前可以使用+号表示正数,不影响返序列化的结果。