前两天出了一道php文件读取和反序列化漏洞相结合的CTF题目。反序列化这个漏洞也是CTF的常客了,印象里第一次接触这个漏洞还是大二还是什么时候的PCTF。年代过于久远,但是当时因为并不是特别懂PHP,只是能看懂,并不会写,就一直搁置了这个问题。后来学完PHP才去了解这个漏洞,发现并不难,并不怎么需要php基础。
php序列化与反序列化
序列化就是把一个对象变为一个字符串,方便存储传输,反序列化就是将这个字符串再变成对象。
php中有两个函数serialize和unserialize分别用来序列化和反序列化。
举一个栗子:
1 |
|
输出如下:
1 | O:5:"willv":3:{s:3:"str";s:5:"willv";s:3:"arr";a:2:{s:5:"first";s:3:"1st";s:6:"second";s:3:"2nd";}s:3:"num";i:18;} |
输出的第一行就是对$class1
对象序列化生成的字符串,从左至右的意思分别是:
1 | O -> (object)表示存储的是对象(也有可能是数组,就是a了) |
php反序列化漏洞
php反序列化漏洞也叫php对象注入漏洞。当unserialize
内容可控就可以触发反序列化漏洞,控制php内部对象变量以及函数。
魔术方法
在反序列化漏洞中,有4个魔术方法比较重要。(并不是其他的不重要,只不过这四个直接关系到序列化和反序列化本身,如果有其他的魔术方法也可以使用)
1 | __construct()当一个对象创建时被调用 |
1 |
|
1 |
|
两次__destruct
是分别对序列化的对象和反序列化的对象进行析构,反序列化并不会触发__construct
构造。
wakeup
unserialize()
后__wakeup()
和__destruct()
会自动调用。如果服务器能够接收我们反序列化过的字符串、并且未经过滤的把其中的变量直接放进这些魔术方法里面的话,就容易造成很反序列化漏洞。
1 |
|
针对上面的代码,我们就可以构造:
1 |
|
序列化为:O:5:"willv":1:{s:3:"str";s:7:"hacked!";}
请求?test=O:5:"willv":1:{s:3:"str";s:7:"hacked!";}
,页面输出hacked!
并不是willv
。这就是最简单的一种php反序列化漏洞的利用。
当然,除了__wakeup
以外,很多函数都可以利用。
其他魔术方法
虽然反序列化只直接调用__wakeup()
和__destruct()
,但并不意味着__construct()
等不可利用。有时候反序列化一个对象时,由它调用的__wakeup()
中又去调用了其他的对象,由此可以溯源而上,找到漏洞点。
如:
1 |
|
这里就触发了__construct()
。
普通成员方法
前面谈到的利用都是基于“自动调用”的魔术方法。但当漏洞/危险代码存在类的普通方法中,就不能指望通过“自动调用”来达到目的了。这时的利用方法如下,寻找相同的函数名,把敏感函数和类联系在一起。
1 |
|
这个代码,正常情况下,是不会执行到类test2
的。使用相同的函数名构造:
1 |
|
得到O:5:"willv":1:{s:4:"test";O:5:"test2":1:{s:4:"here";s:5:"bingo";}}
传参利用成功。
phar扩大攻击面
phar常常与文件上传和文件读取相结合在一起。phar的利用我最开始是在2019TSec腾讯安全探索论坛上了解到的,当时演讲者利用一个mysql的漏洞结合phar实现了RCE,去除漏洞细节的PPT也公布了:Comprehensive analysis of the mysql client attack chain.pdf)
2018年blackhat大会上,来自Secarma的安全研究员Sam Thomass发现了一种新的漏洞利用方式,可以在不使用php函数unserialize()的前提下,引起严重的php反序列化漏洞。
通常我们在利用反序列化漏洞的时候,只能将序列化后的字符串传入unserialize()
,随着代码安全性越来越高,利用难度也越来越大。
phar
phar结构
大多数PHP文件操作允许使用各种URL协议去访问文件路径:如data://
,zlib://
或php://
。phar://
也是流包装的一种,它由4部分组成:
1. a stub
可以理解为一个标志,格式为xxx<?php xxx;__HALT_COMPILER();?>
,前面内容不限,但必须以__HALT_COMPILER();?>
来结尾,否则phar扩展将无法识别这个文件为phar文件。
2. a manifest describing the contents
phar文件本质上是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的meta-data,这是上述攻击手法最核心的地方。
3. the file contents
被压缩文件的内容。
[optional] a signature for verifying Phar integrity (phar file format only)
签名,放在文件末尾,格式如下:
demo
根据文件结构我们来自己构建一个phar文件,php内置了一个Phar类来处理相关操作
注意:要将php.ini中的phar.readonly选项设置为Off,否则无法生成phar文件。
1 |
|
访问这个php,会在目录下生成一个phar.phar
文件。
使用编辑器可以查看这个文件,可以明显的看到meta-data是以序列化的形式存储的:
有序列化数据必然会有反序列化操作,php一大部分的文件系统函数在通过phar://
伪协议解析phar文件时,都会将meta-data进行反序列化,测试后受影响的函数如下:
这里,就可以触发反序列化漏洞了。
1 |
|
当文件系统函数的参数可控时,我们可以在不调用unserialize()的情况下进行反序列化操作,一些之前看起来“人畜无害”的函数也变得“暗藏杀机”,极大的拓展了攻击面。
将phar伪造成其他格式的文件
在前面分析phar的文件结构时可能会注意到,php识别phar文件是通过其文件头的stub,更确切一点来说是__HALT_COMPILER();?>
这段代码,对前面的内容或者后缀名是没有要求的。那么我们就可以通过添加任意的文件头+修改后缀名的方式将phar文件伪装成其他格式的文件。
1 |
|
采用这种方法可以绕过很大一部分上传检测。
2020腾讯T-Star犀牛鸟靶场赛Phar反序列化
手里只有writeup了,题目代码当时没保存。
题目是文件包含Getshell
,提示文件包含phar
。
index注释中有lfi.txt,里面是lfi.php的源码。提示了phar,那么构造phar并生成。因为只能上传txt,就把后缀改成txt上传。
访问http://92d5b8bf.yunyansec.com/lfi.php?file=phar://files/TyzMmm61j7UxZnCD.txt/shell&x=ls
成功列目录。看到flag.php,
读取http://92d5b8bf.yunyansec.com/lfi.php?file=phar://files/TyzMmm61j7UxZnCD.txt/shell&x=cat%3C%3Eflag.php
(因为这里参数传不了空格,就用<>,即%3C%3E来代替空格执行命令)得到flag。
phar代码如下:
1 |
|
(当时这道题我记得有人的WP是使用zip伪协议做的)
利用
任何漏洞或攻击手法不能实际利用,都是纸上谈兵。在利用之前,先来看一下这种攻击的利用条件。
phar文件要能够上传到服务器端。
如
file_exists()
,fopen()
,file_get_contents()
,file()
等文件操作的函数要有可用的魔术方法作为“跳板”。
文件操作函数的参数可控,且
:
、/
、phar
等特殊字符没有被过滤。
具体利用我这里就不写了,可以看CSS2019的PPT,或者wordpress和漏洞验证。
防御
- 在文件系统函数的参数可控时,对参数进行严格的过滤。
- 严格检查上传文件的内容,而不是只检查文件头。
- 在条件允许的情况下禁用可执行系统命令、代码的危险函数。
参考
【CSS2019】Comprehensive analysis of the mysql client attack chain.pdf)
File Operation Induced Unserialization via the “phar://” Stream Wrapper