前言
概念:
这其实是为了解决 PHP 对象传递的一个问题,因为 PHP 文件在执行结束以后就会将对象销毁,那么如果下次有一个页面恰好要用到刚刚销毁的对象就会束手无策,总不能你永远不让它销毁,等着你吧,于是人们就想出了一种能长久保存对象的方法,这就是 PHP 的序列化,那当我们下次要用的时候只要反序列化一下就 ok 啦
序列化的目的是方便数据的传输和存储. json 是为了传递数据的方便性.。
一、序列化与反序列化
序列化:
函数 : serialize()
把复杂的数据类型压缩到一个字符串中 数据类型可以是数组,字符串,对象等
序列化一个对象将会保存对象的所有变量,但是不会保存对象的方法,只会保存类的名字。
反序列化:
函数: unserialize()
恢复原先被序列化的变量
二、魔术函数
__construct 当一个对象创建时被调用,
__destruct 当一个对象销毁时被调用,
__toString 当一个对象被当作一个字符串被调用。
__wakeup() 使用unserialize时触发
__sleep() 使用serialize时触发
__destruct() 对象被销毁时触发
__call() 在对象上下文中调用不可访问的方法时触发
__callStatic() 在静态上下文中调用不可访问的方法时触发
__get() 用于从不可访问的属性读取数据
__set() 用于将数据写入不可访问的属性
__isset() 在不可访问的属性上调用isset()或empty()触发
__unset() 在不可访问的属性上使用unset()时触发
__toString() 把类当作字符串使用时触发,返回值需要为字符串
__invoke() 当脚本尝试将对象调用为函数时触发
1.序列化
class people{
public $name = 'sam';
private $sex = 'man';
protected $age = '20';
}
$people1 = new people();
$object = serialize($people);
print_r($object);
定义一个people类,含有公共属性name,私有属性sex,保护属性age。随后实例化一个people1,并对其进行序列化,最后输出结果为:
O:6:"people":3:{s:4:"name";s:3:"sam";s:11:" people sex";s:3:"man";s:6:" * age";s:2:"20";}
2.反序列化
<?php
class people{
public $name = 'sam';
private $sex = 'man';
protected $age = '20';
}
$people1 = new people();
$object = serialize($people1);
$a=unserialize($object);
#print_r($object);
var_dump($a);
输出为:
object(people)#2 (3) { ["name"]=> string(3) "sam" ["sex":"people":private]=> string(3) "man" ["age":protected]=> string(2) "20" }
这里需要说明一下,对于public的属性无需其他特殊操作,但是对于private属性,描述的时候需要在前后添加空格,或者带上其所在的类名,对于protected属性需要加" * "。
3.几个魔法函数的调用
1. __wakeup() 当unserialize()函数反序列化时,在数据流还未被反序列化未对象之前会调用该函数进行初始化.
2. __destruct() 当对象销毁时触发,也就是说只要你反序列化或者实例化一个对象,当你调用结束后都会触发该函数。
<?php
class star{
public $a;
function __wakeup(){
echo "hi";
}
function __destruct(){
echo "结束了";
}
}
$s='O:4:"star":1:{s:1:"a";N;}';
unserialize($s);
结果:hi结束了
可以看到,先输出了hi,说明__wakeup()函数先被调用,随后整个反序列化结束,对象被销毁,触发__destruct()函数,输出结束了。
3. __toString() 当一个对象被当作字符串使用时触发。
<?php
class star{
public $a;
function __wakeup(){
echo $this->a;
}
}
class next{
function __toString(){
echo "我在这";
}
}
$t='O:4:"star":1:{s:1:"a";O:4:"next":0:{}}';
unserialize($t);
结果:我在这 Catchable fatal error: Method next::__toString() must return a string value in /tmp/41bac5636b55eff5c8abea138d605489916c2612abc45fd39fdaa87a827a0e00/main.php on line 5
这里没有retrun,也没有忽略报错,所有有一条报错信息,无关紧要,但是要说的是,__toString()是要又return的,不然会报错。结果显示当类star中的
echo $this->a;
执行时,a被当作一个字符串,此时我将a设置为类next,此时类next作为字符串被调用,所以触发类next中的__toString()函数,输出“我在这”。
4. __invoke() 当类被当作函数调用时触发,看实例。
<?php
class star{
public $a;
function __wakeup(){
$function=$this->a;
return $function();
}
}
class next{
function __invoke(){
echo "我在这";
}
}
$t='O:4:"star":1:{s:1:"a";O:4:"next":0:{}}';
unserialize($t);
结果:我在这
分析过程和上面那个函数一样,也是通过反序列化给a赋值,只是赋的不是字符串而是其他类,然后
return $function();
的时候,将类当作函数调用,触发了__invoke()函数输出了“我在这”。
5. __get() 这个函数是当访问不可访问的属性的时候触发,不可访问的属性有两种
- 私有属性或者保护属性,这种访问受限的属性的时候会触发__get()
- 属性不存在的时候,也会触发__get()
<?php
class star{
public $a;
function __wakeup(){
return $this->str['str']->source;
}
}
class next{
function __get($name){
echo "我在这";
return;
}
}
$t='O:4:"star":2:{s:1:"a";N;s:3:"str";a:1:{s:3:"str";O:4:"next":0:{}}}';
unserialize($t);
结果:我在这
通过str[‘str’]赋值为类next,访问next的source,但是类next中不存在属性source所以触发__get()函数,访问保护属性等同理。
小结:反序列化的过程通过这些魔法函数可以达到我们想到要的操作,尤其是后面3个函数,大家会发现,这三个函数可以达到多个类的连续使用,从而达到链的效果,这也就是反序列化中的pop链的编写,具体pop链的知识可以看下面这篇:
POP链的构造
接下来我们讲一下反序列化的漏洞
三、反序列化漏洞
1.__wakeup( )绕过
(CVE-2016-7124)
反序列化时,如果表示对象属性个数的值大于真实的属性个数时就会跳过__wakeup( )的执行。
影响版本:
PHP before 5.6.25
7.x before 7.0.10
<?php
class star{
public $a;
function __wakeup(){
echo $this->a;
}
}
$t='O:4:"star":1:{s:1:"a";s:9:"我在这";}';
unserialize($t);
结果:我在这
O:4:"star":2:{s:1:"a";s:9:"我在这";}
结果:无输出
结果显示,当表示属性个数大于真实个数时,__wakeup()函数不执行,被绕过了,通常题目中,__wake()中含有很多限制,通过这个漏洞绕过__wake()可以达到绕过限制的目的。
2.POP链
总结
1.构造反序列化链
2.魔法函数
3.pop链
4.__wakeup()绕过