Skip to content

Instantly share code, notes, and snippets.

@GioBonvi
Created September 6, 2019 09:44
Show Gist options
  • Save GioBonvi/b94278d0519dfa46f96f3de354efe269 to your computer and use it in GitHub Desktop.
Save GioBonvi/b94278d0519dfa46f96f3de354efe269 to your computer and use it in GitHub Desktop.
A simple tool to decode, verify or generate a signed Flask session cookie. Useful during CTFs.
#!/usr/bin/env python3
# encoding: utf-8
"""
Flask session cookie toolkit.
Decode, verify or generate a signed Flask session cookie.
Credits to Terry Vogelsang for the original script (https://terryvogelsang.tech/MITRECTF2018-my-flask-app/)
which I just slightly modified for my personal use.
"""
from hashlib import sha512
from flask.sessions import session_json_serializer
from itsdangerous import URLSafeTimedSerializer, BadTimeSignature
import argparse
import base64
from zlib import decompress
import sys
import json
# GENERAL FUNCTIONS.
def debug(msg):
if VERBOSE_OUTPUT:
print("[DEBUG] " + msg)
def pretty_print_json_data(json_data):
json_pretty_str = json.dumps(json_data, indent=4)
print(json_pretty_str)
# COOKIE DECODER.
def decode_cookie_payload(cookie):
debug(f"Cookie:\n{cookie}")
# If the cookie starts with a dot the paylod is base64 encoded and GZIP compressed.
if cookie[0] == ".":
b64_gzip_payload = cookie[1:].split(".")[0]
# Python needs the padding which is stripped in the base64_URLsafe version (see https://stackoverflow.com/questions/2941995/python-ignore-incorrect-padding-error-when-base64-decoding#comment12174484_2942039)
b64_gzip_payload += "=" * (-len(b64_gzip_payload) % 4)
debug(f"Encoded and compressed payload:\n{b64_gzip_payload}")
gzip_payload = base64.urlsafe_b64decode(b64_gzip_payload)
payload = decompress(gzip_payload)
debug(f"Decoded and decompressed payload:\n{payload}")
else:
# If the cookie does not start with a dot the payload is just base64 encoded.
b64_payload = cookie.split(".")[0]
# Python needs the padding which is stripped in the base64_URLsafe version (see https://stackoverflow.com/questions/2941995/python-ignore-incorrect-padding-error-when-base64-decoding#comment12174484_2942039)
b64_payload += "=" * (-len(b64_payload) % 4)
debug(f"Encoded payload:\n{b64_payload}")
payload = base64.urlsafe_b64decode(b64_payload)
debug(f"Decoded payload:\n{payload}")
return payload
def output_decoded_payload(payload, json_pretty, str_encoding):
str_payload = payload.decode(str_encoding)
if json_pretty:
# Pretty print the JSON data.
try:
debug(f"Payload string (encoding=\"{str_encoding}\"):\n{str_payload}")
pretty_print_json_data(json.loads(str_payload))
except:
print("The payload is not valid JSON!", file=sys.stderr)
sys.exit(1)
else:
# Do not pretty print the JSON data.
print(str_payload)
sys.exit(0)
# COOKIE VERIFIER.
def readAndVerifyCookie(cookie, secret_key):
debug(f"Cookie:\n{cookie}")
signer = URLSafeTimedSerializer(
secret_key, salt="cookie-session",
serializer=session_json_serializer,
signer_kwargs={"key_derivation": "hmac", "digest_method": sha512}
)
try:
session_data = signer.loads(cookie)
print("The signature is correct!")
return session_data
except BadTimeSignature:
print(f"The signature is not correct!")
sys.exit(1)
# COOKIE GENERATOR.
def generate_cookie(json_str_payload, key):
try:
payload = json.loads(json_str_payload)
except:
print("Your payload is not a valid JSON string!")
sys.exit(1)
signer = URLSafeTimedSerializer(
key, salt="cookie-session",
serializer=session_json_serializer,
signer_kwargs={"key_derivation": "hmac", "digest_method": sha512}
)
cookie = signer.dumps(payload)
return cookie
# MAIN.
if __name__ == "__main__":
# Argparse setup.
argparser = argparse.ArgumentParser(description="Pefroma various actions regarding a Flask session COOKIE.")
argparser.add_argument("command", metavar="COMMAND", choices=["decode", "verify", "generate"], help="the command to execute")
argparser.add_argument("-c", "--cookie", metavar="COOKIE", help="the COOKIE to decode or verify")
argparser.add_argument("-k", "--key", metavar="SECRET_KEY", help="the SECRET_KEY to sign or verify the cookie with")
argparser.add_argument("-p", "--payload", metavar="PAYLOAD", help="the PAYLOAD to encode in teh cookie")
argparser.add_argument("-v", "--verbose", action="store_true", help="enable verbose output")
argparser.add_argument("--pretty-json", action="store_true", help="whether to pretty print the JSON data")
argparser.add_argument("--encoding", default="UTF-8", help="the ENCODING to use when parsing data as a string")
# Parse arguments.
args = argparser.parse_args()
command = args.command
VERBOSE_OUTPUT = args.verbose
# Choose command.
if command == "decode":
# Check arguments.
if args.cookie is None:
argparser.error("The 'decode' command requires the --cookie argument.")
cookie = args.cookie
json_pretty = args.pretty_json
str_encoding = args.encoding
# Decode.
payload = decode_cookie_payload(cookie)
# Output.
output_decoded_payload(payload, json_pretty, str_encoding)
elif command == "verify":
# Check arguments.
if args.cookie is None or args.key is None:
argparser.error("The 'verify' command requires both the --cookie and the --key arguments.")
cookie = args.cookie
key = args.key
pretty_json = args.pretty_json
# Verify.
session_data = readAndVerifyCookie(cookie, key)
# Output.
print('')
if pretty_json:
debug(f"Session data:\n{session_data}")
pretty_print_json_data(session_data)
else:
print(json.dumps(session_data))
elif command == "generate":
# Check arguments.
if args.payload is None or args.key is None:
argparser.error("The 'generate' command requires both the --payload and the --key arguments.")
payload = args.payload
key = args.key
# Generate.
cookie = generate_cookie(payload, key)
# Output.
print(cookie)
Flask>=1.1.1
itsdangerous>=1.1.0
@kev1n
Copy link

kev1n commented Jun 10, 2023

If this doesn't work try SHA1. I think it's the default for flask as of right now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment