反序列化

基础

  • 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
    <?php
    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
2
3
4
5
string 's:5:"hello";' (length=12)
string 'i:123;' (length=6)
string 'b:0;' (length=4)
string 'O:4:"test":2:{s:1:"a";N;s:1:"b";s:6:"hellob";}' (length=46)
string 'O:5:"test1":2:{s:1:"a";s:5:"hello";s:1:"b";N;}' (length=46)
字母 类型
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
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
34
35
36
37
38
39
<?php
class test{
var $a;
var $b;
function __construct($a,$b,$c){
$this->a = $a;
$this->b = $b;

}
function __sleep(){
echo "b has changed"."\n";
$this->b = 'hib';
return $this->b;


}
function __wakeup(){
echo "a has changed"."\n";
$this->a = 'hia';

}
}

class test1 extends test{

function __construct($a){
$this->a = $a;
}
}

$d = new test('helloa','hellob','helloc');
$e = new test1('hello');

serialize($d);
serialize($e);

var_dump($d);
var_dump($e);
?>

执行结果

1
2
3
4
5
6
7
b has changed b has changed
object(test)[2]
public 'a' => string 'helloa' (length=6)
public 'b' => string 'hib' (length=3)
object(test1)[2]
public 'a' => string 'hello' (length=5)
public 'b' => string 'hib' (length=3)

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
34
35
36
37
38
39
40
41
<?php
class test
{
var $a;
var $b;
function __construct($a, $b, $c)
{
$this->a = $a;
$this->b = $b;

}
function __sleep()
{
echo "b has changed" . "\n";
$this->b = 'hib';
return $this->b;


}
function __wakeup()
{
echo "a has changed" . "\n";
$this->a = 'hia';

}
}

class test1 extends test
{

function __construct($a)
{
$this->a = $a;
}
}

$d = 'O:4:"test":2:{s:1:"a";N;s:1:"b";s:6:"hellob";}';
$e = 'O:5:"test1":2:{s:1:"a";s:5:"hello";s:1:"b";N;}';

var_dump(unserialize($d));
var_dump(unserialize($e));

运行结果

1
2
3
4
5
6
7
8
a has changed
object(test)[2]
public 'a' => string 'hia' (length=3)
public 'b' => string 'hellob' (length=6)
a has changed
object(test1)[1]
public 'a' => string 'hia' (length=3)
public 'b' => null

其他

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class File
{
function __construct($var, $file1, $file2)
{
$this->var = $var;
$this->file1 = $file1;
$this->file2 = $file2;
echo $this->var . ' and ' . $this->file1 . ' and ' . $this->file2 . 'defined';
}
function __destruct()
{
unlink(dirname(__FILE__) . '/' . $this->file1);
echo $this->file1 . 'deleted';
}
function __toString()
{
return file_get_contents($this->file2);
}
}
// $file = new File('hello','123.txt','456.php');
// var_dump(serialize($file));
echo unserialize('O:4:"File":3:{s:3:"var";s:5:"hello";s:5:"file1";s:7:"123.txt";s:5:"file2";s:7:"456.php";}');
  • __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
2
3
4
5
6
// 这一步将设置session的值 |O:5:"lemon":1:{s:2:"hi";s:14:"echo "spoock";";}
<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$_SESSION["spoock"]=$_GET["a"];
?>

然后’php_serialize’将会设置session对话并且值为a:1:{s:6:”spoock”;s:48:”|O:5:”lemon”:1:{s:2:”hi”;s:14:”echo “spoock”;”;}”;}

然后访问test2.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
ini_set('session.serialize_handler', 'php');
session_start();
class lemon {
var $hi;
function __construct(){
$this->hi = 'phpinfo();';
}

function __destruct() {
eval($this->hi);
}
}
?>

这一步将会利用解析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
2
3
4
<?php
var_dump(unserialize('O:+4:"test":1:{s:1:"a";s:3:"aaa";}'));
var_dump(unserialize('O:4:"test":1:{s:1:"a";s:3:"aaa";}'));
?>
打赏
0%