- 什么是PHP序列化
- PHP序列化与反序列化的过程
- 一个反序列化漏洞的例子
- CVE-2016-7124
一. 什么是PHP序列化与反序列化
1. PHP序列化
PHP序列化是指把变量转化成可保存或传输的字符串的过程,PHP序列化函数有serialize
、json_encode
。
以下例子可以实现PHP序列化
1 |
|
输出的结果为
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 | __construct() #类的构造函数 |
2. 如何进行序列化
在对象被序列化之前,会检查是否有__sleep()
函数,如果存在,该函数会清理对象,并返回一个数组,数组中包含被序列化的对象的所有属性的名称。如果该方法不返回任何内容,则序列化后的字符串将变为N并提示Notice。__sleep()
的预期用途是提交需要挂起的数据或执行类似的清理任务。如果有一个非常大的对象,不需要完全保存其所有属性,该功能将非常有用。
在反序列化之前,会检查是否具有__wakeup()
魔术方法。如果存在该方法,则在反序列化时执行该方法。__wakeup()
魔术方法可以重构对象可能具有的任何资源。__wakeup()
预期用途是重新建立在序列化期间可能已丢失的任何数据库连接,并执行其他重新初始化任务。
1 |
|
1 | //output |
3. __autoloading()函数
传统的PHP只能反序列化定义过的类,意味着每个PHP文件都需要包含很多文件,在当前主流的PHP框架中,都采用了__autoloading()
自动加载类来完成这项繁重的工作。在简化了工作的同时,也为序列化漏洞造成了便捷。
4. 反序列化过程中魔术方法的执行顺序
__wakeup()
> __toString()
> __destruct()
三. 一个反序列化漏洞的例子
题目地址:welcome to bugkuctf
右击查看源代码,可以看到如下提示
1 | $user = $_GET["txt"]; |
首先我们尝试将$user
的值改为welcome to the bugkuctf,由于使用的是file_get_contents()
函数,所以需要使用PHP伪协议来传入$user
的值,payload如下
下一步处理$file
的值,可以看到代码中有文件包含,并提示了所包含的文件时hint.php,这里需要使用另一个PHP伪协议来传入$file
的值,payload如下
将出现的字符通过base64解密,可以得到如下代码
1 |
|
可以看到,在flag.php文件中有一个Flag
类,此外,我们可以用同样的方法得到index.php文件中的内容
1 | $txt = $_GET["txt"]; |
其中有一段代码时这样的
1 | if(preg_match("/flag/",$file)){ |
意思就是说如果直接引用flag.php这个文件是不会显示flag的值的。
同时,还发现了另一段代码
1 | include($file); |
这个else写到如果我的$file
不包含flag.php,那么我就会把那个文件包含进来,之后将password反序列化一下。并输出password的结果。而我们根据上面的hint.php发现,在Flag
类被创建时,__tostring()
函数会自动执行,同时如果引用的文件存在,会输出文件中的内容。所以,构造如下payload
查看源代码,即可看到flag
四. CVE-2016-7124
1. 漏洞分析
该漏洞存在于PHP5小于5.6.25版本或PHP7小于7.0.10版本中,该漏洞简单来说就是当序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup()
的执行,demo如下
1 | <html> |
由于__wakeup()
的执行顺序在__destruct()
之前,所以__wakeup()
会将对象内的所有属性设为NULL,在__destruct()
执行时,没有内容会写到文件中。但使用漏洞,可以跳过__wakeup()
,直接执行__destruct()
,这样可以将属性内容写入文件中。
如果我们使用如下payload
1 | test=O:4:"test":1:{s:4:"name";s:18:"<?php phpinfo();?>";} |
会执行__wakeup()
函数,页面上会出现如下输出
使用如下payload
1 | test=O:4:"test":2:{s:4:"name";s:18:"<?php phpinfo();?>";} |
执行后,会将<?php phpinfo();?>
写入hello.php文件中。