PHP中的序列化serialize()与反序列化unserialize()

0×00 PHP中的序列化与反序列化

序列化:是将对象的状态信息转换为可以存储或传输的形式的过程

反序列化:把字节序列还原为对象的过程称为反序列化。


0×01 序列化与反序列化的作用

序列化的存在是为了使对象可以以某种形式存储,和进行网络传输,使程序更具维护性。

因为跨平台存储和进行网络传输支持的数据格式是字节数组,因此我们需要通过一种特定的规则将对象转换成字节数组,这个规则就是序列化,而通过这个规则还原对象的过程就是反序列化。

就好比将一个搭建好的大型积木打乱的过程是序列化,这个过程使我们能够将打乱后的积木块放在盒子中,方便携带积木、存放积木并将积木进行转移(将对象从一个地方传递到另一个地方),而将打乱的积木重新搭建好的过程就是反序列化。


0×02 序列化格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
class Person{
private $name = 'ShiWang';
private $sex = 'woman';

function say($name,$sex){
echo 'My name is'.$name.'I am a '.$sex;
}
}

$Person = new Person;
echo serialize($Person);
?>

//输出:
O:6:"Person":2:{s:12:"Personname";s:7:"ShiWang";s:11:"Personsex";s:5:"woman";}

格式:
对象类型 : 对象名长度 : “对象名” : 对象成员变量个数 : { 变量1类型 : 变量名1长度 : 变量名1 ; 参数1类型 : 参数1长度 : 参数1 ; 变量2类型 : 变量名2长度 : “变量名2” ; 参数2类型 : 参数2长度 : 参数2 ; … … }

对象\变量\参数类型:

1
2
3
4
5
6
7
8
9
10
11
12
O - Class
a - Array
s - string
i - int
b - boolean
d - double
o - common object
r - reference
C - custom object
N - null
R - pointer reference
U - unicode string

序列符号:参数与变量之间用分号(;)隔开,同一变量和同一参数之间的数据用冒号(:)隔开。


0×03 反序列化漏洞成因

PHP反序列化漏洞又称PHP对象注入,是因为程序对输入数据处理不当导致的。漏洞的根源在于unserialize()函数的参数可控。如果反序列化对象中存在魔术方法,而且魔术方法中的代码或变量用户可控,就可能产生反序列化漏洞,根据反序列化后不同的代码可以导致各种攻击,如代码注入、SQL注入、目录遍历等等。


0×04 魔术方法(Magic methods)

PHP 将所有以 __(两个下划线)开头的类方法保留为魔术方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
__construct()  类的构造函数,在对象创建时自动调用
__destruct() 类的析构函数,在脚本运行结束时自动调用
__call() 在对象中调用一个不可访问方法时调用,
__callStatic() 用静态方式中调用一个不可访问方法时调用,
__get() 获得一个类的成员变量时调用,
__set() 设置一个类的成员变量时调用,
__isset() 当对不可访问属性调用isset()或empty()时调用,即类外检测一个私有属性时
__unset() 当对不可访问属性调用unset()时被调用,即类外删除一个私有属性时
__sleep() 执行serialize()时,先会调用这个函数。此方法必须有返回内容,否则NULL被序列化,并产生一个E_NOTICE级别的错误
__wakeup() 执行unserialize()时,先会调用这个函数,
__toString() 类被当成字符串时的回应方法,类被当成字符串时的回应方法,不能在此方法中抛出异常,否则会导致致命错误
__invoke() 调用函数的方式调用一个对象时的回应方法,
__set_state() 调用var_export()导出类时,此静态方法会被调用,
__clone() 当对象复制完成时调用
__autoload() 尝试加载未定义的类
__debugInfo() 打印所需调试信息


0×05 魔术方法的触发

sleep() 和 wakeup()

serialize() 函数会检查类中是否存在一个魔术方法 __sleep()。如果存在,该方法会先被调用,然后才执行序列化操作。

与之相反,unserialize() 会检查是否存在一个 __wakeup() 方法。如果存在,则会先调用 _ _wakeup() 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
class Test{
function __sleep(){
echo '__sleep()被调用了';
return array();
}
function __wakeup(){
echo '__wakeup()被调用了';
}
}

$s = serialize(new Test);
$a = unserialize($s);
?>

//输出:__sleep()被调用了__wakeup()被调用了

__toString()

__toString() 方法用于一个类被当成字符串时应怎样回应。例如 echo $obj; 应该显示些什么。此方法必须返回一个字符串,否则将发出一条 E_RECOVERABLE_ERROR 级别的致命错误。

1
2
3
4
5
6
7
8
9
10
<?php 
class Test{
function __toString(){
return '类被当作字符串输出';
}
}
$obj = new Test();
echo $obj;
?>
//输出:类被当作字符串输出

__invoke()

当尝试以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用。

1
2
3
4
5
6
7
8
9
10
<?php 
class Test{
function __invoke(){
echo '类被当作函数使用';
}
}
$obj = new Test();
$obj();
?>
//输出:类被当作函数使用


学习链接:PHP之十六个魔术方法详解