基础
- serialize()将一个对象转换成一个字符串
- unserialize()将字符串还原为一个对象
示例代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class test
{
var $a;
var $b;
function __construct($a, $b, $c)
{
$a = $a;
$this->b = $b;
}
}
class test1 extends test
{
function __construct($a)
{
$this->a = $a;
}
}
$a = 'hello';
$b = 123;
$c = false;
$d = new test('helloa', 'hellob', 'helloc');
$e = new test1('hello');
var_dump(serialize($a));
var_dump(serialize($b));
var_dump(serialize($c));
var_dump(serialize($d));
var_dump(serialize($e));
结果
1 | string 's:5:"hello";' (length=12) |
字母 | 类型 |
---|---|
a | array 数组 |
b | boolean布尔型 |
d | double双精度型 |
i | integer |
o | common object一般对象 |
r | reference |
s | string |
C | custom object 自定义对象 |
O | class |
N | null |
R | pointer reference |
U | unicode string unicode编码的字符串 |
- 序列化字符串格式:变量类型:变量长度:变量内容。
- 如果序列化的是一个对象,序列化字符串格式为:
- 变量类型:类名长度:类名:属性数量:{属性类型:属性名长度:属性名;属性值类型:属性值长度:属性值内容}
对象序列化
- 当序列化对象时,PHP 将在序列动作之前调用该对象的成员函数 sleep()。这样就允许对象在被序列化之前做任何清除操作。类似的,当使用 unserialize() 恢复对象时, 将调用wakeup()成员函数。
- 在serialize()函数执行时,会先检查类中是否定义了sleep()函数,如果存在,则首先调用sleep()函数,如果不存在,就保留序列字符串中的所有属性。
- 在unserialize()函数执行时,会先检查是否定义了wakeup()函数。如果wakeup()存在,将执行__wakeup()函数,会使变量被重新赋值。
序列化中字母对应的类型
测试代码
1 | <?php |
执行结果
1 | b has changed b has changed |
unserialize()测试代码
1 | <?php |
运行结果
1 | a has changed |
其他
1 | class File |
- __construct() 在实例化一个对象时被调用,一般用来给属性赋值,
- __destruct() 在实例化对象完成后执行,
- __toString() 函数在echo一个对象时被调用
将字符串反序列化后,由于已经对变量赋过值,那么就不会再执行construct()函数,在construct()中赋值的变量也是无效的。上述代码中destruct()方法在在反序列化后,实例化对象结束后执行了,tostring()函数在echo unserialize()处,也被执行了
PHP Session 序列化及反序列化处理器
PHP 内置了多种处理器用于存取 $_SESSION 数据时会对数据进行序列化和反序列化,常用的有以下三种,对应三种不同的处理格式:
处理器 | 对应的存储格式 |
---|---|
php | 键名 + 竖线 + 经过 serialize() 函数反序列处理的值 |
php_binary | 键名的长度对应的 ASCII 字符 + 键名 + 经过 serialize() 函数反序列处理的值 |
php_serialize (php>=5.5.4) | 经过 serialize() 函数反序列处理的数组 |
配置选项 session.serialize_handler
PHP 提供了 session.serialize_handler 配置选项,通过该选项可以设置序列化及反序列化时使用的处理器:
1 | session.serialize_handler "php" |
安全隐患
通过上面对存储格式的分析,如果 PHP 在反序列化存储的 $_SESSION 数据时的使用的处理器和序列化时使用的处理器不同,会导致数据无法正确反序列化,通过特殊的构造,甚至可以伪造任意数据:)1
$_SESSION['ryat'] = '|O:8:"stdClass":0:{}';
例如上面的 $_SESSION 数据,在存储时使用的序列化处理器为 php_serialize,存储的格式如下:
1 | a:1:{s:4:"ryat";s:20:"|O:8:"stdClass":0:{}";} |
在读取数据时如果用的反序列化处理器不是 php_serialize,而是 php 的话,那么反序列化后的数据将会变成:1
2
3
4
5
6
7#!php
// var_dump($_SESSION);
array(1) {
["a:1:{s:4:"ryat";s:20:""]=>
object(stdClass)#1 (0) {
}
}
可以看到,通过注入 | 字符伪造了对象的序列化数据,成功实例化了 stdClass 对象:)
实际利用
php:存储方式是,键名+竖线+经过serialize()函数序列处理的值1
name|s:6:"spoock"
php_serialize(php>5.5.4):存储方式是,经过serialize()函数序列化处理的值
1 | a:1:{s:4:"name";s:6:"spoock";} |
首先访问test1.php
1 | // 这一步将设置session的值 |O:5:"lemon":1:{s:2:"hi";s:14:"echo "spoock";";} |
然后’php_serialize’将会设置session对话并且值为a:1:{s:6:”spoock”;s:48:”|O:5:”lemon”:1:{s:2:”hi”;s:14:”echo “spoock”;”;}”;}
然后访问test2.php
1 | <?php |
这一步将会利用解析session对话的值,以|为分割符将session分割为键名和值两部分
键名:a:1:{s:6:”spoock”;s:48:”
值O:5:”lemon”:1:{s:2:”hi”;s:14:”echo “spoock”;”;}”;}
从而解析出了序列化,自动生成了lemon的类,当类在结尾销毁时将会调用析构函数执行恶意代码
session.upload_progress.enabled
而当这个选项被打开时,php会自动记录上传文件的进度,在上传时会将其信息保存在$_SESSION中。
其他
1 | <?php |