前言:
什么是php序列化和反序列化?
简单的理解:序列化就是使用serialize()
将对象用字符串的方式进行表示并赋值给变量,反序列化是使用unserialize()
将序列化的后字符串(这里的的字符串就是对象序列化之后的产物)构成相应的对象,反序列化是序列化的逆过程。
- 序列化
serialize()
操作:
serialize() 返回字符串,此字符串包含了表示 value 的字节流,可以存储于任何地方。
简单来讲,就是将对象转化为可以传输的字符串,字符串中存储着对象的变量、类型等。
举个例子:
test.php
<?php
class people{
public $name = "hello";
public $age = "20";
}
$fairy = new people(); //实例化people对象
echo serialize($fairy); // 通过echo的方式输出people序列化后的字符串
?>
最终浏览器输出:
这里来解释下是什么意思:首先O
表示的是一个对象,如果传给serialize()
的值是一个数组,那么这里的O
就会是A
,后面的6
表示当前对象名是6个字符
people
表示对象名,后面的2
表示当前对象中存在2个属性值
, {}里面第一个s
表示的属性名类型是String类型,第二个4
表示属性名name的字符是4个
,以此类推第二个s
表示name的值是String类型
,后面的5表示name值hello的字符是5个
,后面的s:3:“age”;s:2:“20”;的意思也是和上面一样。
- 反序列化unserialize ()操作:
将序列化后的字符串转化为PHP的值。
举个例子:
test.php
<?php
class people{
public $name = "hello";
public $age = "20";
}
$fairy = new people();
$s_fairy = serialize($fairy); //序列化为字符串
$uns_fairy = unserialize($s_fairy); //将字符串$s_fairy进行反序列化为对象
var_dump($uns_fairy); # 打印对象
最终浏览器输出:
魔术方法:
PHP 将所有以 __(两个下划线)开头的类方法保留为魔术方法。所以在定义类方法时,除了上述魔术方法,建议不要以 __ 为前缀。
__construct()
,__destruct()
,__call()
,__callStatic()
,__get()
, __set()
, __isset()
,__unset()
,__sleep()
,__wakeup()
,__toString()
,__invoke()
,__set_state()
,__clone()
和__debugInfo()
等方法在 PHP 中被称为"魔术方法
"(Magic methods)。
这里举例几个常用的魔术方法:
-
__construct()
PHP 5 允行开发者在一个类中定义一个方法作为构造函数。具有构造函数的类会在每次创建新对象时先调用此方法,所以非常适合在使用对象之前做一些初始化工作。 -
__destruct()
析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行。 -
__sleep()
serialize() 函数会检查类中是否存在一个魔术方法 __sleep()。如果存在,该方法会先被调用,然后才执行序列化操作。 -
__wakeup()
unserialize() 会检查是否存在一个 __wakeup() 方法。如果存在,则会先调用 __wakeup 方法,预先准备对象需要的资源。 -
__toString()
__toString() 方法用于一个类被当成字符串时应怎样回应。例如 echo $obj; 应该显示些什么。
以上几个常用的魔术方法使用顺序和使用方法还请一定要牢记主!!!
反序列化漏洞:
__wakeup()的利用场景:
test.php
<?php
class Test{
var $num= "123";
function __wakeup(){
$fp = fopen("test.php", 'w');
fwrite($fp, $this -> num);
fclose($fp);
}
}
$test1 = $_GET['test'];
print_r($test1);
echo "<br />";
$seri = unserialize($test1);
require "test.php";
?>
以上代码的意思就是,首先定义一个Test类
,类成员有$num=123
,并且存在__wakeup()
魔术方法,该方法的功能是打开一个test.php
文件,并且将Test类
中的变量$num
写入到test.php
中,然后通过GET的方式传入参数test
赋值给$test1
,最终将$test1
传入unserialize()函数来进行反序列化操作。那么这里如果没有对传入unserialize()函数的值$test1进行过滤
的话,就会导致反序列化漏洞。
exp如下:
O:4:"Test":1:{s:3:"num";s:18:"<?php%20phpinfo();?>";}
此时就会生成
test.php
文件,并且内容为<?php phpinfo();?>
。
当然了这里只是演示了当存在__wakeup()魔术方法的利用方式,除了该魔术方法的利用,php反序列化中其他魔术方法的利用也是非常多的,这里就不一一举例了。
__wakeup()魔术方法绕过(CVE-2016-7124)
-
漏洞影响版本:
PHP5 < 5.6.25
PHP7 < 7.0.10 -
漏洞产生原因:
如果存在__wakeup
方法,调用unserilize()
方法前则先调用__wakeup
方法,但是序列化字符串中表示对象属性个数的值大于 真实的属性个数时会跳过__wakeup的执行
-
漏洞复现
test.php
<?php
class car{ //定义car类
public $name ="benchi"; //$name属性
function __wakeup(){
echo "this is __wakeup"."<br/>";
}
function __destruct(){
echo "this is __destruct"."<br/>";
}
}
$str=$_GET['s']; //GET方式接收参数赋值到$str
$un_str=unserialize($str); //将字符串进行反序列化,那么我们传入的字符串$str就要是序列化之后的值
echo $un_str->name."<br/>";
?>
脚本通过GET
方式传入参数s赋值给$str
,对其反序列化后输出name属性的值
为了方便观察,我将传入的s参数的name属性值更改为xss代码
页面显示语句代表反序列化之前先调用了__wakeup 方法
点击确定后,页面完成后自动执行__destruct方法
将传入的序列化数据的对象变量个数由1更改为2,页面只执行了__destruct方法,而且输出name属性时报错,是由于反序列化数据时失败无法创建对象。
所以
序列化字符串中表示对象属性个数的值大于 真实的属性个数时就会跳过__wakeup的执行
而直接执行__destruct方法。
总结:
通过上面的实验可以看出,不管是反序列化漏洞还是CVE-2016-7124漏洞的利用,最根本的还是通过unserialize()函数来进行反序列化时,没有对传入的参数进行过滤所导致的。只要记住一点,所以由用户输入的值都是不可靠的,经过合理的过滤和处理,也就可以很好的预防此类漏洞的产生。