Phar协议与反序列化漏洞

这次柏鹭杯其中的一道web,题目并不难,就是利用这个知识点。做的时候依稀记得有一个点,可是不能上网最后没做出来还是太菜了TAT。这篇博客先复现一下这个知识点,然后以hitcon2017一道题来练习一下。

phar协议

        phar:// 主要以前见过的还是本地文件包含。 ?file=phar://../avatar.gif/shell.php 包含avatar.gif(本来是个zip或者phar文件改了后缀上传)中的shell.php文件。但这篇博客主要是讲解利用phar协议造成反序列化漏洞。

漏洞原理

​         phar文件本质上是一种压缩文件,在使用phar协议文件包含时,也是可以直接读取zip文件的。使用phar://协议读取文件时,文件会被解析成phar对象,phar对象内的以序列化形式存储的用户自定义元数据(metadata)信息会被反序列化。这就引出了我们攻击手法最核心的流程。 构造phar(元数据中含有恶意序列化内容)文件-->上传-->触发反序列化 ​ 最后一步是寻找触发phar文件元数据反序列化。其实php中有一大部分的文件系统函数在通过phar://伪协议解析phar文件时都会将meta-data进行反序列化。

漏洞测试

        假如我们现在有这么一份代码
        我们有一个file_get_contents函数可以触发phar://协议解析phar文件时,将其中的元数据反序列化。

生成phar文件

        首先得生成一个含有序列化metadata的phar文件。php提供一个类允许我们处理phar文件相关操作。注意要将php.ini中的phar.readonly选项设置为Off。

将phar文件上传到服务器

​        phar文件是很容易绕过上传限制的,首先它的后缀是不限制的,改成什么phar://协议都可以解析。 ​ 而且,我们来看一下上小结生成的phar文件
        我们不但看到了元数据,我们还在开头看到了<?php xxx; __HALT_COMPILER();?> 这样子的代码,这就对应生成phar文件代码中的$phar->setStub("<?php __HALT_COMPILER(); ?>"); 可以理解为这时phar文件的一个标志,否则phar扩展无法识别这个文件为phar文件。 ​ 但实验证明,前面这个标志的格式为xxx<?php xxx; __HALT_COMPILER();?> 前面内容不限,这样我们就可以在前面添加注入’GIF98a’这样的文件头绕过上传限制。

反序列化执行

​         以上步骤都完成了的话,直接执行我们测试的那份代码,phar://协议在file_get_contents函数中解析phar文件,将元数据反序列化执行魔法函数。

一道CTF题继续理解漏洞利用

​         hitcon2017baby^h-master-php-2017就用这个知识点出题了,只是在那个时候这个漏洞还相当于是个0day,hitcon这道题当时比赛的时候据说也是0解(当然这道题也不止利用了这一个知识点)。 ​ 本题现在可以通过i春秋平台复现,地址:http://117.50.3.97:8005

分析代码

​         代码一开始创建了一个读flag的匿名函数,并且初始化了沙箱。
        代码中有两个类User、Admin,Admin继承自User。而且在Admin类中的魔法函数中存在eval()函数和$_GET["lucky"]();eval函数里面定义了一个函数,运行这个函数也能拿到flag,这样就可以利用lucky输入函数名调用函数。虽然在check_session函数中也存在反序列化操作,但是有很强的限制,实际上它就是一个干扰,我们是无法通过check_session中的反序列化操作反序列化我们自己的序列化数据的。 ​
        经观察,这题还有一个upload函数,我们可以通过这个upload函数上传phar文件。其中只有一个限制,就是文件开头必须为’GIP89a’,我们可以按照上面的方法修改phar文件前6个字节为’GIF89a’。再次访问时,利用file_get_contents函数反序列化我们注入phar文件元数据的序列化Admin类。然后执行Admin魔法函数里面的代码。​
​        做到这与我们本篇博客相关的知识已经用完,但其实这道题并没有做完。在Admin类中eval中的函数名是随机的,我们无法预测$random的值的话,就无法使用$_GET["lucky"](); 调用该函数。
​         其实,php中的匿名函数还是有名字的, 格式为\0lambda_%d %d格式化为本进程第n个匿名函数。 我们如果能找到代码第一行创建的读flag匿名函数的名字,那我们可以在$_GET["lucky"]();中直接调用它,拿到flag。 ​ 这就涉及到另一个知识点:
Apache-prefork模型(默认模型)在接受请求后会如何处理,首先Apache会默认生成5个child server去等待用户连接, 默认最高可生成256个child server, 这时候如果用户大量请求, Apache就会在处理完MaxRequestsPerChild个tcp连接后kill掉这个进程,开启一个新进程处理请求。
当开启新进程时,那么我们在第一行创建的匿名函数应该是第一个匿名函数。那么我们就可以通过lucky=%00lambda_1访问到该函数。

开始利用

  1. 生成符合要求的phar文件放到自己的vps中。
  2. 请求?m=upload&url=http://xxx.xxx.xxx.xxx (将phar文件上传到赛题服务器)
  3. 大量请求赛题服务器,使得获取flag的函数为%00lambda_1

生成phar文件

 
本地生成后,修改名称为avatar.gif上传到vps,然后访问?m=upload&url=http://xxx.xxx.xxx.xxx 可以看到Upload OK

大量请求apache

这一点Orange大佬已经给出了。

获取flag

访问http://117.50.3.97:8005/?m=upload&url=phar:///var/www/data/05840bcb0eaf84ae56ded3b9ea49cabc&lucky=%00lambda_1 就可以拿到了flag了。注意后面的地址要根据自己的ip生成。

发表评论

电子邮件地址不会被公开。 必填项已用*标注