1. 개요
race condition 문제
2. 분석
코드를 보면, buy_noodle, buy_soup에 약 1초간 sleep이 걸려있다.
만약, 재빠르게 buy_noodle, buy_soup을 진행하면, balance 가 업데이트되기 전에 구매가 가능하다.
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()
3. exploit
DB를 사용했으면 isolation level을 serializable로 사용했겠지
'CTF' 카테고리의 다른 글
[SECCON - Beginners_CTF_2021] magic (0) | 2023.05.28 |
---|---|
[SECCON - Beginners_CTF_2021] json (0) | 2023.05.28 |
[SECCON - Beginners_CTF_2021] osoba (0) | 2023.05.28 |
[SECCON - Beginners_CTF_2021] werewolf (0) | 2023.05.28 |
[SCTF 2022] CUSES (0) | 2023.05.21 |