SECCON Beginners CTF 2021 writeup(web:cant_use_db)

※本サイトはアフィリエイト広告を利用しています。
広告

SECCON Beginners CTFと本記事について

2021年5月22日と2021年5月23日に開催された「SECCON Beginners CTF 2021」の「web」の「cant_use_db」問題について、解きましたので、手順を詳細に記載します。

web

問題文は以下の通りです。
SECCON Beginners CTF 2021_web_cant_use_db(1)

問題のWebサイトにアクセスすると、以下の画面が表示されます。
SECCON Beginners CTF 2021_web_cant_use_db(2)

また、提供されたファイルを解凍すると、様々なファイルがありました。
プログラムの箇所のみ表示すると、以下の2つのファイルがありました

<!doctype html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Can't use DB.</title>
    <link rel="stylesheet" media="all" href="static/css/ress.min.css" />
    <link rel="stylesheet" media="all" href="static/css/style.css" />
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
<header>
    <div class="container">
        <div class="row">
            <div class="col span-12">
                <div class="head">
                <h1><a href="javascript:alert('🍜')">Hack ramen 🍜</a></h1>
                </div>
            </div>
        </div>
    </div>
</header>
<div class="mainimg">
    <div class="container">
        <div class="row">
            <center>
            <div class="col span-6">
                <img src="static/img/ramen.jpg" alt="ramen">
            </div>
            </center>
        </div>
    </div>
</div>
<main>
<section>
    <div class="container">
        <div class="row">
            <div class="col span-6">
                <div style="padding: 10px; margin-bottom: 10px; border: 1px dotted #333333;">
                <center>
                <h2  class="catch">UserData (texts)</h2>
                <h5>Balance: ${{info.balance}}</h5>
                <h5>Noodles: {{info.noodles}}/2</h5>
                <h5>Soup: {{info.soup}}/1</h5>
                <h5>Wallet: <font size="4">{{info.user_id}}</font></h5>
                </center>
                </div>
            </div>
            <div class="col span-6">
                <center>
                <h2  class="catch">Store</h2>
                Noodles $10000<br>
                <button style="width:80%;" onclick="$.post('/buy_noodles', function(data){alert(data);location.reload();});">Buy</button><br>
                Soup $20000<br>
                <button style="width:80%;" onclick="$.post('/buy_soup', function(data){alert(data);location.reload();});;">Buy</button><br>
                <font color="red">Flag</font><br>(Noodles>=2, Soup>=1)<br>
                <button style="width:80%;" onclick="location.href='./eat';">😋</button><br>
                </center>
            </div>
        </div>
    </div>
</section> 
</main>
</body>
</html>
import os
import re
import time
import random
import shutil
import secrets
import datetime
from flask import Flask, render_template, session, redirect

app = Flask(__name__)
app.secret_key = secrets.token_bytes(256)


def init_userdata(user_id):
    try:
        os.makedirs(f"./users/{user_id}", exist_ok=True)
        open(f"./users/{user_id}/balance.txt", "w").write("20000")
        open(f"./users/{user_id}/noodles.txt", "w").write("0")
        open(f"./users/{user_id}/soup.txt", "w").write("0")
        return True
    except:
        return False


def get_userdata(user_id):
    try:
        balance = open(f"./users/{user_id}/balance.txt").read()
        noodles = open(f"./users/{user_id}/noodles.txt").read()
        soup = open(f"./users/{user_id}/soup.txt").read()
        return [int(i) for i in [balance, noodles, soup]]
    except:
        return [0] * 3


@app.route("/")
def top_page():
    user_id = session.get("user")
    if not user_id:
        dirnames = datetime.datetime.now()
        user_id = f"{dirnames.hour}{dirnames.minute}/" + secrets.token_urlsafe(30)
        if not init_userdata(user_id):
            return redirect("/")
        session["user"] = user_id
    userdata = get_userdata(user_id)
    info = {
        "user_id": re.sub("^[0-9]*?/", "", user_id),
        "balance": userdata[0],
        "noodles": userdata[1],
        "soup": userdata[2]
    }
    return render_template("index.html", info = info)


@app.route("/buy_noodles", methods=["POST"])
def buy_noodles():
    user_id = session.get("user")
    if not user_id:
        return redirect("/")
    balance, noodles, soup = get_userdata(user_id)
    if balance >= 10000:
        noodles += 1
        open(f"./users/{user_id}/noodles.txt", "w").write(str(noodles))
        time.sleep(random.uniform(-0.2, 0.2) + 1.0)
        balance -= 10000
        open(f"./users/{user_id}/balance.txt", "w").write(str(balance))
        return "💸$10000"
    return "ERROR: INSUFFICIENT FUNDS"


@app.route("/buy_soup", methods=["POST"])
def buy_soup():
    user_id = session.get("user")
    if not user_id:
        return redirect("/")
    balance, noodles, soup = get_userdata(user_id)
    if balance >= 20000:
        soup += 1
        open(f"./users/{user_id}/soup.txt", "w").write(str(soup))
        time.sleep(random.uniform(-0.2, 0.2) + 1.0)
        balance -= 20000
        open(f"./users/{user_id}/balance.txt", "w").write(str(balance))
        return "💸💸$20000"
    return "ERROR: INSUFFICIENT FUNDS"


@app.route("/eat")
def eat():
    user_id = session.get("user")
    if not user_id:
        return redirect("/")
    balance, noodles, soup = get_userdata(user_id)
    shutil.rmtree(f"./users/{user_id}/")
    session["user"] = None
    if (noodles >= 2) and (soup >= 1):
        return os.getenv("CTF4B_FLAG")
    if (noodles >= 2):
        return "The noodles seem to get stuck in my throat."
    if (soup >= 1):
        return "This is soup, not ramen."
    return "Please make ramen."


if __name__ == "__main__":
    app.run()


動作を確認するために画面とプログラムを対応付けると、以下のようになります。
「buy_noodles()」関数を2回と「buy_soup()」関数を1回実行して、「eat()」関数を実行したらフラグが表示されます。
しかし、予算が「$20000」に対して、「Noodle $10000×2」と「Soup $20000」の合計「$40000」必要なため通常の購入すると予算が足りません。
SECCON Beginners CTF 2021_web_cant_use_db(3)

@app.route("/buy_noodles", methods=["POST"])
def buy_noodles():
ーーーーー(中略)ーーーーー
    if balance >= 10000:
        noodles += 1
        open(f"./users/{user_id}/noodles.txt", "w").write(str(noodles))
        time.sleep(random.uniform(-0.2, 0.2) + 1.0)
        balance -= 10000
        open(f"./users/{user_id}/balance.txt", "w").write(str(balance))
        return "💸$10000"
    return "ERROR: INSUFFICIENT FUNDS"

@app.route("/buy_soup", methods=["POST"])
def buy_soup():
ーーーーー(中略)ーーーーー
    if balance >= 20000:
        soup += 1
        open(f"./users/{user_id}/soup.txt", "w").write(str(soup))
        time.sleep(random.uniform(-0.2, 0.2) + 1.0)
        balance -= 20000
        open(f"./users/{user_id}/balance.txt", "w").write(str(balance))
        return "💸💸$20000"
    return "ERROR: INSUFFICIENT FUNDS"

@app.route("/eat")
def eat():
    user_id = session.get("user")
ーーーーー(中略)ーーーーー
    if (noodles >= 2) and (soup >= 1):
        return os.getenv("CTF4B_FLAG")
ーーーーー(中略)ーーーーー


「buy_noodles()」と「buy_soup()」関数を確認したところ、「time.sleep(random.uniform(-0.2, 0.2) + 1.0)」にて処理を一時停止しています。
そのため、購入処理が完了する前に、「buy_noodles()」関数のボタン2回と「buy_soup()」関数のボタンを1回実行実行しました。
SECCON Beginners CTF 2021_web_cant_use_db(4)

上記の通り、購入できましたので、eatボタンを押します。
そうすると、フラグが表示されました。
SECCON Beginners CTF 2021_web_cant_use_db(5)

SECCON Beginners CTF 2021に関する記事

SECCON Beginners CTF 2021についての関連記事は以下の通りです。
必要に応じて、ご確認ください。

Noタイトル記事の概要
1SECCON Beginners CTF 2021 writeup以下のカテゴリの問題のwriteupを記載します。
【カテゴリ:welcome】
  ・welcome
【カテゴリ:crypto】
  ・imple_RSA
【カテゴリ:reversing】
  ・only_read
  ・children
【カテゴリ:pwnable】
  ・rewriter
【カテゴリ:web】
  ・osoba
【カテゴリ:misc】
  ・git-leak
  ・Mail_Address_Validator
2SECCON Beginners CTF 2021 writeup
(crypto:simple_RSA)
cryptカテゴリのsimple_RSAの問題の
writeupを記載します。
3SECCON Beginners CTF 2021 writeup
(crypto:Logical_SEESAW)
cryptカテゴリのLogical_SEESAWの問題の
writeupを記載します。
4SECCON Beginners CTF 2021 writeup
(crypto:GFM)
cryptカテゴリのGFMの問題の
writeupを記載します。
5SECCON Beginners CTF 2021 writeup
(crypto:Imaginary)
cryptカテゴリのImaginaryの問題の
writeupを記載します。
6SECCON Beginners CTF 2021 writeup
(web:Werewolf)
webカテゴリのWerewolfの問題の
writeupを記載します。
7SECCON Beginners CTF 2021 writeup
(web:check_url)
webカテゴリのcheck_urlの問題の
writeupを記載します。
8SECCON Beginners CTF 2021 writeup
(web:json)
webカテゴリのjsonの問題の
writeupを記載します。
9SECCON Beginners CTF 2021 writeup
(web:cant_use_db)
webカテゴリのcant_use_dbの問題の
writeupを記載します。