PHP反序列化漏洞
本章概述:这是一篇关于PHP反序列化漏洞的文章
1、PHP序列化和反序列化
在学习PHP反序列化漏洞之前,我们有必要先来了解一下这两个函数,serialize()
和unserialize()
,熟悉PHP的大佬都知道,这两个是序列化和反序列化函数,那什么是序列化和反序列化。根据官方手册,所有php里面的值都可以使用函数serialize()
来返回一个包含字节流的字符串来表示。unserialize()
函数能够重新把字符串变回php原来的值。 序列化一个对象将会保存对象的所有变量,但是不会保存对象的方法,只会保存类的名字。那么简单来说,序列化就是把一个对象变成可以传输的字符串,反序列化就是把序列化后的字符串还原成对象。
序列化示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class S{
public $test="github";
}
$s=new S(); //创建一个对象
serialize($s); //把这个对象进行序列化
echo serialize($s)
/**
上述代码会返回 O:1:"S":1:{s:4:"test";s:6:"github";}字符串。
o:代表一个object
第一个1:代表对象名字长度为一个字符
S:对象的名称
第二个1:代表对象里面有一个变量
s:数据类型
4:变量名称长度
test:变量名称
s:数据类型
6:变量值的长度
github:变量值
**/
2、反序列化漏洞
需要注意的是,序列化和反序列化本身没有问题,但是如果反序列化的内容是用户可以控制的,且后台不正当的使用了PHP中的魔法函数,就会导致安全问题。我们来了解一下几个常见的魔法函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 __construct()当一个对象创建时被调用
__destruct()当一个对象销毁时被调用
__toString()当一个对象被当作一个字符串使用
__sleep() 在对象在被序列化之前运行
__wakeup()在被反序列化之前先调用该函数
漏洞示例:
class S{
var $test = "github";
function __destruct(){
echo $this->test;
}
}
$s = $_GET['test'];
@$unser = unserialize('O:1:"S":1:{s:4:"test";s:29:"<script>alert("xss")</script>";}');
//上面的代码反序列化函数里面的东西是用户自己输入的,当用户输入该payload时,函数执行完就会弹出一个框,而之所以会这样,就是因为没有对用户输入的内容进行控制。
根据上面所说的内容,我们可以总结出该漏洞利用的条件:unserialize函数的参数用户可控,所写的内容需要有对象中的成员变量的值,脚本中存在魔法函数。
3、漏洞解析
漏洞样例:phpMyAdmin 2.x中存在的反序列化漏洞,漏洞位置在/scripts/setup.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 define( 'PMA_MINIMUM_COMMON', TRUE );
chdir('..');
require_once('./libraries/common.lib.php'); //引入该php文件
// Grab configuration defaults
$PMA_Config = new PMA_Config(); //创建了PMA_Config对象,PMA_Config就是对象名
// Script information
$script_info = 'phpMyAdmin ' . $PMA_Config->get('PMA_VERSION') . ' setup script by Michal ?iha? <michal@cihar.com>';
$script_version = '$Id$';
// Grab action
if (isset($_POST['action'])) {
$action = $_POST['action'];
} else {
$action = '';
}
if (isset($_POST['configuration']) && $action != 'clear' ) {
//如果configuration存在并且action不为clear,则对configuration进行反序列化操作
// Grab previous configuration, if it should not be cleared
$configuration = unserialize($_POST['configuration']); //反序列化
} else {
// Start with empty configuration
$configuration = array();
}
// We rely on Servers array to exist, so create it here
if (!isset($configuration['Servers']) || !is_array($configuration['Servers'])) {
$configuration['Servers'] = array();
}
//代码上面重要部分有注释
上面的这段代码简单来说,就是通过输入一个序列化字符,会反序列化成一个对象,但是并没有看到魔法函数。
因此我们接着看引入的文件./libraries/common.lib.php
1 | require_once './libraries/sanitizing.lib.php'; |
这个文件又引入了其他文件,我们主要看Config.class.php,这个才是重点文件。
./libraries/Config.class.php:
1 | function __wakeup() //魔法函数__wakeup,该魔法函数在对象被序列化之后立即被触发调用: |
在这个文件里面,我们找到魔法函数__wakeup()
,并且通过代码解析我们可以发现,满足魔法函数的条件时会调用load
函数,我们进一步继续跟踪load函数会发现传入了一个source
变量,并且在load
函数中有几行重要的代码,也是漏洞的关键位置。我将这几行代码单独放在下面来看一看。
1 | if ( function_exists('file_get_contents') ) { |
当检测到file_get_contents
被定义,则通过eval
函数执行读入的字符串;如果没有file_get_contents
函数,则通过file
读入文件,同时利用implode
函数把文件内容利用\n
拼接,再执行eval
函数。
那么整个代码的分析过程就已经完成了,通过上面的解析,我们可以发现,当我们输入一个序列化字符串,在被反序列化成一个对象之前,会先触发__wakeup()
函数,并且满足该魔法函数内的要求时,则可以进行任意读取文件或其他操作。
接下来我们所需要的就是构造我们需要的payload。
在setup.php文件中,我们需要两个传参字段,action
和configuration
,同时创建了对象PMA_Config
。
并且在load函数中传入source参数。
1 |
|
我们得到的序列化的字符串为:O:10:"PMA_Config":1:{s:6:"source";s:11:"/etc/passwd";}
可以看到上面的payload把etc下的passwd文件给读取出来了。
4、CTF样题
这里顺便附上一道Bugku的CTF样题。
题目地址:flag.php,提示为:hint
这是一个点击登录完全没效果的页面,按照提示,我给了个hint=111的参数,页面显示源码。源码如下:
1 |
|
我把源码给简单过滤了一下不需要看的部分
1 |
|
源码还是很容易就看得懂的,首先取出名为ISecer的cookie,然后对该cookie进行反序列化操作,等于给定的值时则输出flag。
不过还是被这个题目给小小的坑了一下,其实也不算坑吧,是我自己PHP没有学好哈哈,我一开始用下面给定的$KEY='ISecer:www.isecer.com';
进行序列化后传给ISecer,但是一直不行,后来才发现在php源码中并没有定义这个KEY的值,因此这个值应该为空,即“”。
1 |
|
得到的结果为s:0:"";
成功取出flag,这道题本身也很容易,算是对反序列化漏洞的运用吧。