SECCON Beginners CTFと本記事について
2021年5月22日と2021年5月23日に開催された「SECCON Beginners CTF 2021」の「web」の「cant_use_db」問題について、解きましたので、手順を詳細に記載します。
web
問題文は以下の通りです。
問題のWebサイトにアクセスすると、以下の画面が表示されます。
また、提供されたファイルを解凍すると、様々なファイルがありました。
プログラムの箇所のみ表示すると、以下の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」必要なため通常の購入すると予算が足りません。
@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回実行実行しました。
上記の通り、購入できましたので、eatボタンを押します。
そうすると、フラグが表示されました。
SECCON Beginners CTF 2021に関する記事
SECCON Beginners CTF 2021についての関連記事は以下の通りです。
必要に応じて、ご確認ください。
No | タイトル | 記事の概要 |
---|---|---|
1 | SECCON Beginners CTF 2021 writeup | 以下のカテゴリの問題のwriteupを記載します。 【カテゴリ:welcome】 ・welcome 【カテゴリ:crypto】 ・imple_RSA 【カテゴリ:reversing】 ・only_read ・children 【カテゴリ:pwnable】 ・rewriter 【カテゴリ:web】 ・osoba 【カテゴリ:misc】 ・git-leak ・Mail_Address_Validator |
2 | SECCON Beginners CTF 2021 writeup (crypto:simple_RSA) | cryptカテゴリのsimple_RSAの問題の writeupを記載します。 |
3 | SECCON Beginners CTF 2021 writeup (crypto:Logical_SEESAW) | cryptカテゴリのLogical_SEESAWの問題の writeupを記載します。 |
4 | SECCON Beginners CTF 2021 writeup (crypto:GFM) | cryptカテゴリのGFMの問題の writeupを記載します。 |
5 | SECCON Beginners CTF 2021 writeup (crypto:Imaginary) | cryptカテゴリのImaginaryの問題の writeupを記載します。 |
6 | SECCON Beginners CTF 2021 writeup (web:Werewolf) | webカテゴリのWerewolfの問題の writeupを記載します。 |
7 | SECCON Beginners CTF 2021 writeup (web:check_url) | webカテゴリのcheck_urlの問題の writeupを記載します。 |
8 | SECCON Beginners CTF 2021 writeup (web:json) | webカテゴリのjsonの問題の writeupを記載します。 |
9 | SECCON Beginners CTF 2021 writeup (web:cant_use_db) | webカテゴリのcant_use_dbの問題の writeupを記載します。 |