2026-07-12 20:22:15 +01:00

93 lines
3.0 KiB
Python

#!/usr/bin/env python3
"""
Guess the Seed (Weak PRNG) - CTF challenge server
Behavior:
- On startup the server picks a secret_seed = int(time.time()).
- It generates a short sequence of tokens:
token_i = sha256(str( random.Random(secret_seed + i).getrandbits(64) ).encode()).hexdigest()
for i = 0..N-1
- The server displays observed tokens (first K tokens) and a time window [secret_seed - WINDOW, secret_seed + WINDOW]
for players to brute-force the seed.
- The correct next-token is token_K (the next sequence element).
- Hitting /flag?token=<predicted> returns the flag if predicted matches token_K.
This is intentionally weak for CTF use.
"""
import time
import random
import hashlib
import os
from flask import Flask, jsonify, render_template, request, abort
app = Flask(__name__)
# Configuration
NUM_TOKENS = 5 # total tokens generated on startup
OBSERVED_TOKENS = 3 # number of tokens shown to the player (first K)
WINDOW = 3600 # +/- seconds around secret seed to publish as candidate window (1 hour)
FLAG_FILE = "flag.txt"
# Initialize state on startup
secret_seed = int(time.time())
tokens = []
def make_token_from_seed(s):
"""Generate a token deterministically from a seed (int)."""
rnd = random.Random(int(s))
val = rnd.getrandbits(64)
h = hashlib.sha256(str(val).encode()).hexdigest()
# shorten token to 16 hex chars to be nicer for players
return h[:16]
for i in range(NUM_TOKENS):
t = make_token_from_seed(secret_seed + i)
tokens.append(t)
# target is next token after observed tokens
target_token = tokens[OBSERVED_TOKENS] # token the player must predict
@app.route("/")
def challenge():
"""Return observed tokens and window data (player-facing)."""
start = secret_seed - WINDOW
end = secret_seed + WINDOW
obs = tokens[:OBSERVED_TOKENS]
return render_template("challenge.html",
observed=obs,
window_start=start,
window_end=end,
note=f"{OBSERVED_TOKENS} tokens shown — predict the next token (the {OBSERVED_TOKENS+1}th).")
@app.route("/api/observed")
def api_observed():
"""Machine-readable API returning observed tokens and window."""
start = secret_seed - WINDOW
end = secret_seed + WINDOW
return jsonify({
"observed": tokens[:OBSERVED_TOKENS],
"window_start": start,
"window_end": end,
"note": f"{OBSERVED_TOKENS} tokens shown — predict the next token (the {OBSERVED_TOKENS+1}th)."
})
@app.route("/flag")
def flag():
"""Return flag if token matches the predicted next token."""
t = request.args.get("token", "")
if not t:
abort(400, "Missing token parameter.")
if t == target_token:
if os.path.exists(FLAG_FILE):
with open(FLAG_FILE, "r") as f:
return f.read().strip() + "\n"
else:
return "FLAG_MISSING\n"
else:
abort(403, "Invalid token")
# Simple health endpoint
@app.route("/health")
def health():
return "ok\n"