#!/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= 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"