php反序列化漏洞专题
PHP反序列化漏洞
序列化与反序列化
PHP的序列化
php序列化就是将各种类型的数据,压缩并按照一定的格式进行存储的过程。其本质就是将对象数据类型转换成字符串的数据类型,方便于对象的传递。
函数:serialize()
代码实例:
1 |
|
输出结果:
结果分析:
O:代表这是一个对象
13:代表对象名称占13个字符
SerializeDemo:代表对象名称
3:代表对象有3个属性
s:代表属性的类型str
1:代表属性名称长度为1字符
a:代表属性名称
s:代表属性值类型为str
6:代表属性值占6个字符
public:代表属性值
注:根据结果我们可以发现这里面的三个属性的标识结果均不相同,这是为什么呢?
PHP序列化中的权限
根据上述实例我们发现,属性b在序列化之后变成了SerializeDemob,长度也与真实字符数量不一致;并且属性c也不相同,前面多了一个*。发生此种现象的原因主要是由于成员变量属性的属性不同所导致的。序列化为了能把整个类对象的各种信息完完整整的压缩、格式化,必须也要对其属性的权限进行序列化。针对于此种情况,我们具体来看一下public、private、protected三种属性的具体表现情况:
- public
该权限序列化时,该是几个字符就是几个字符
- private
该权限是私有属性,私有权限的特征是该类对其私有属性具有强烈占有欲,所以会在其私有属性前增加自己的类名;多出来的两个字符为空白符,也就是%00,最终的表现形式为:%00类名%00属性名
- protected
该属性为受保护权限,序列化时的变现形式为:%00*%00属性名
PHP序列化的内容
根据前面序列化的实例我们发现,整个序列化的过程中,只会对其类的属性内容进行序列化保存,不会对其方法进行序列化。
- 在反序列化的时候一定要保证在当前的作用域环境下有该类存在;
反序列化就是将我们压缩格式化的对象还原成初始状态的过程(可以认为是解压缩的过程),因为我们没有序列化方法,因此在反序列化以后我们如果想正常使用这个对象的话我们必须要依托于这个类要在当前作用域存在的条件。
- 在反序列化攻击的时候也就是依托类属性进行攻击;
因为没有序列化方法,能控制的只有类的属性,因此类属性就是唯一的攻击入口。在的攻击流程中,就是要寻找合适的能被我们控制的属性,然后利用它本身的存在的方法,在基于属性被控制的情况下发动我们的反序列化攻击。
PHP的反序列化
反序列化就是将原本序列化之后的字符串格式还原成的初始对象的过程;使对象数据长久保存,可以在随时想调用的时候就可以调用。
函数:unserialize()
代码实例:
1 |
|
serialize.txt
1 | O:13:"SerializeDemo":3:{s:1:"a";s:6:"public";s:16:"%00Serialize%00Demob";s:7:"private";s:4:"%00*%00c";s:9:"protected";} |
结果输出
反序列化漏洞概念
PHP 反序列化漏洞又叫做 PHP 对象注入漏洞。
反序列化漏洞的成因在于代码中的 unserialize() 接收的参数可控,从上面的例子看,这个函数的参数是一个序列化的对象,而序列化的对象只含有对象的属性,那我们就要利用对对象属性的篡改实现最终的攻击。
魔法函数
常见的魔法函数
- __construct()
当对象创建时会自动调用(但在unserialize()时是不会自动调用的)。
- __destruct()
当对象被销毁时会自动调用。
- __toString()
当反序列化后的对象被输出在模板中的时候(转换成字符串的时候)自动调用。
(1)echo (
$obj
) / print($obj
) 打印时会触发(2)反序列化对象与字符串连接时
(3)反序列化对象参与格式化字符串时
(4)反序列化对象与字符串进行==比较时(PHP进行==比较的时候会转换参数类型)
(5)反序列化对象参与格式化SQL语句,绑定参数时
(6)反序列化对象在经过php字符串函数,如 strlen()、addslashes()时
(7)在in_array()方法中,第一个参数是反序列化对象,第二个参数的数组中有toString返回的字符串的时候toString会被调用
(8)反序列化的对象作为 class_exists() 的参数的时候
- __wakeup()
当一个对象被反序列时会被自动调用。
- __sleep()
对象被序列化之前触发,返回需要被序列化存储的成员属性,删除不必要的属性。
- __invoke()
当一个实例化对象被当成一个方法调用时会自动触发此方法。
- __call()
在一个对象中,当一个调用的方法不能访问时,会自动触发。
- 2.2.8 __get()
当调用一个不可访问的属性时会自动调用。
利用魔法函数的攻击实例(magic_function)
案例代码
1 |
|
代码分析
首先发现有三个类,主类为Greetdawn
,且该类中有__construct()、__destruct()
两个魔法函数;
实例化Greetdawn
类的时候,会先触发__construct()
方法,实例化一个L类并赋值给变量test
;
L
类中含有action
方法且输出一句话;
我们发现在L类下方还存在一个Evil
的类也同样存在一个action
方法,但是该方法中是调用一个eval
的危险函数;
那这边强制将__construct()
方法中实例化的类改为Evil
类即可达成利用;
exp
1 |
|
反序列化攻击流程
(1)寻找unserialize()函数的参数是否有我们的可控点
(2)寻找我们的反序列化的目标,重点寻找 存在 wakeup() 或 destruct() 魔法函数的类
(3)一层一层地研究该类在魔法方法中使用的属性和属性调用的方法,看看是否有可控的属性能实现在当前调用的过程中触发的
(4)找到我们要控制的属性了以后我们就将要用到的代码部分复制下来,然后构造序列化,发起攻击
__wakeup函数绕过
漏洞源码
1 |
|
审计思路
- 源码最后提示,
KEY
在flag.php
里面; - 注意到
__destruct
魔术方法中,有这么一段代码,将file
文件内容显示出来show_source(dirname(FILE).’/‘.$this->file)
,这个是解题关键; - 若
POST“file”
参数为序列化对象,且将file
设为flag.php
;那么可以通过unserialize
反序列化,进而调用destruct
魔术方法来显示flag.php
源码(要注意的是file参数内容需要经过base64编码); - 但是通过
unserialize
反序列化之后,也会调用__wakeup方法
,它会把file设为index.php
; - 总结下来就是,想办法把file设为flag.php,调用
__destruct
方法,且绕过__wakeup
。
__wakeup绕过特性
绕过wakeup的限制:当序列化字符串中,表示对象属性个数的值大于实际属性个数时,那么就会跳过wakeup方法的执行。
php版本限制:PHP5 < 5.6.25 | PHP7 < 7.0.10
file是protected属性,因此需要用\00*\00来表示,\00代表ascii为0的值payload
漏洞利用
因php版本原因未复现可以简单的使用phpstudy来复现比较快捷;
1 |
|
php-unserialize-getflag
题目源码
1 |
|
题目分析
php
反序列化真要被ctf
给玩烂了
这道题的代码不多,但是反正我是不会
逻辑思路是有,但是不会构造的,接下来就简单的分析一下吧
利用类func
中的__destruct()
魔法函数,调用触发类GetFlag
中get_flag
方法,根据$a('', $this->code);
这行代码,可以构造create_function
函数触发,逻辑都很简单。这里唯独就是不知道怎么给类func
中的key构造传参
后面才知道可以这么传参:$key = serialize([new GetFlag(), "get_flag"]);
构造利用
1 |
|
output:
1 | http://127.0.0.1/10279.php?0=O:4:%22func%22:3:{s:4:%22mod1%22;N;s:4:%22mod2%22;N;s:3:%22key%22;s:116:%22a:2:{i:0;O:7:%22GetFlag%22:2:{s:4:%22code%22;s:14:%22;}phpinfo();//%22;s:6:%22action%22;s:15:%22create_function%22;}i:1;s:8:%22get_flag%22;}%22;} |