什么是PHP序列化
PHP序列化与反序列化的过程
一个反序列化漏洞的例子
CVE--7124
一. 什么是PHP序列化与反序列化
1. PHP序列化
PHP序列化是指把变量转化成可保存或传输的字符串的过程,PHP序列化函数有serialize、json_encode。
以下例子可以实现PHP序列化
1
2
3
4
5
6
7
8
9
10
11
12
13<?php
class test
{
public $name = 'jacky';
public $age = '18';
public $heavy = '81.5';
public $boolean = true;
public $null = NULL;
public $array = array(1,2,3,4,5);
}
$jacky = new test;
echo serialize($jacky);
?>
输出的结果为
O:4:”test”:6:{s:4:”name”;s:5:”jacky”;s:3:”age”;s:2:”18”;s:5:”heavy”;s:4:”81.5”;s:7:”boolean”;b:1;s:4:”null”;N;s:5:”array”;a:5:{i:0;i:1;i:1;i:2;i:2;i:3;i:3;i:4;i:4;i:5;}}
其中每项的标识所代表的含义
标识
含义
s
字符串(string)
i
整型(integer)
b
布尔(boolean)
d
双精度(double)
N
空(NULL)
a
数组(array)
同时,序列化过程中还会对不同属性的变量进行不同方式的变化
public的属性在序列化时,直接显示属性名
protected的属性在序列化时,会在属性名前增加0x00*0x00,其长度会增加3
private的属性在序列化时,会在属性名前增加0x00classname0x00,其长度会增加类名长度+2
2. 反序列化
PHP反序列是指将经过序列化的数据转化成原先的状态,PHP反序列化函数有unserialize、json_decode。
二. PHP序列化与反序列化的过程
1. PHP魔法函数
PHP中包含很多魔法函数,他们可以在某些情况下自动执行而不需要手动调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16__construct() #类的构造函数
__destruct() #类的析构函数
__call() #在对象中调用一个不可访问方法时调用
__callStatic() #用静态方式中调用一个不可访问方法时调用
__get() #获得一个类的成员变量时调用
__set() #设置一个类的成员变量时调用
__isset() #当对不可访问属性调用isset()或empty()时调用
__unset() #当对不可访问属性调用unset()时被调用。
__sleep() #执行serialize()时,先会调用这个函数
__wakeup() #执行unserialize()时,先会调用这个函数
__toString() #类被当成字符串时的回应方法
__invoke() #调用函数的方式调用一个对象时的回应方法
__set_state() #调用var_export()导出类时,此静态方法会被调用。
__clone() #当对象复制完成时调用
__autoload() #尝试加载未定义的类
__debugInfo() #打印所需调试信息
2. 如何进行序列化
在对象被序列化之前,会检查是否有__sleep()函数,如果存在,该函数会清理对象,并返回一个数组,数组中包含被序列化的对象的所有属性的名称。如果该方法不返回任何内容,则序列化后的字符串将变为N并提示Notice。__sleep()的预期用途是提交需要挂起的数据或执行类似的清理任务。如果有一个非常大的对象,不需要完全保存其所有属性,该功能将非常有用。
在反序列化之前,会检查是否具有__wakeup()魔术方法。如果存在该方法,则在反序列化时执行该方法。__wakeup()魔术方法可以重构对象可能具有的任何资源。__wakeup()预期用途是重新建立在序列化期间可能已丢失的任何数据库连接,并执行其他重新初始化任务。
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<?php
class test
{
public $value;
function __construct(){
echo "__construct";
echo "
";
}
function __destruct(){
echo "__destruct";
echo "
";
}
function __wakeup(){
echo "__wakeup";
echo "
";
}
function __toString(){
echo "__toString";
echo "
";
return $this->value;
}
function setValue($parm){
echo "setValue";
echo "
";
$this->value = $parm;
}
}
$test = new test;
$test->setValue("Jacky");
$ser_test = serialize($test);
echo $ser_test."
";
$obj = unserialize($ser_test);
echo $obj."
";
?>
1
2
3
4
5
6
7
8
9
10//output
__construct
setValue
O:4:"test":1:{s:5:"value";s:5:"Jacky";}
__wakeup
__toString
Jacky
__destruct
__destruct
3. __autoloading()函数
传统的PHP只能反序列化定义过的类,意味着每个PHP文件都需要包含很多文件,在当前主流的PHP框架中,都采用了__autoloading()自动加载类来完成这项繁重的工作。在简化了工作的同时,也为序列化漏洞造成了便捷。
4. 反序列化过程中魔术方法的执行顺序
__wakeup()> __toString()> __destruct()
三. 一个反序列化漏洞的例子
右击查看源代码,可以看到如下提示
1
2
3
4
5
6
7
8
9
10$user = $_GET["txt"];
$file = $_GET["file"];
$pass = $_GET["password"];
if(isset($user)&&(file_get_contents($user,'r')==="welcome to the bugkuctf")){
echo "hello admin!
";
include($file); //hint.php
}else{
echo "you are not admin ! ";
}
首先我们尝试将$user的值改为welcome to the bugkuctf,由于使用的是file_get_contents()函数,所以需要使用PHP伪协议来传入$user的值,payload如下
下一步处理$file的值,可以看到代码中有文件包含,并提示了所包含的文件时hint.php,这里需要使用另一个PHP伪协议来传入$file的值,payload如下
将出现的字符通过base64解密,可以得到如下代码
1
2
3
4
5
6
7
8
9
10
11
12
13<?php
class Flag{//flag.php
public $file;
public function __tostring(){
if(isset($this->file)){
echo file_get_contents($this->file);
echo "
";
return ("good");
}
}
}
?>
可以看到,在flag.php文件中有一个Flag类,此外,我们可以用同样的方法得到index.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$txt = $_GET["txt"];
$file = $_GET["file"];
$password = $_GET["password"];
if(isset($txt)&&(file_get_contents($txt,'r')==="welcome to the bugkuctf")){
echo "hello friend!
";
if(preg_match("/flag/",$file)){
echo "不能现在就给你flag哦";
exit();
}else{
include($file);
$password = unserialize($password);
echo $password;
}
}else{
echo "you are not the number of bugku ! ";
}
?>