EzFlask
import uuid
from flask import Flask, request, session
from secret import black_list
import json
app = Flask(__name__)
app.secret_key = str(uuid.uuid4())
def check(data):
for i in black_list:
if i in data:
return False
return True
def merge(src, dst):
for k, v in src.items():
if hasattr(dst, '__getitem__'):
if dst.get(k) and type(v) == dict:
merge(v, dst.get(k))
else:
dst[k] = v
elif hasattr(dst, k) and type(v) == dict:
merge(v, getattr(dst, k))
else:
setattr(dst, k, v)
class user():
def __init__(self):
self.username = ""
self.password = ""
pass
def check(self, data):
if self.username == data['username'] and self.password == data['password']:
return True
return False
Users = []
@app.route('/register',methods=['POST'])
def register():
if request.data:
try:
if not check(request.data):
return "Register Failed"
data = json.loads(request.data)
if "username" not in data or "password" not in data:
return "Register Failed"
User = user()
merge(data, User)
Users.append(User)
except Exception:
return "Register Failed"
return "Register Success"
else:
return "Register Failed"
@app.route('/login',methods=['POST'])
def login():
if request.data:
try:
data = json.loads(request.data)
if "username" not in data or "password" not in data:
return "Login Failed"
for user in Users:
if user.check(data):
session["username"] = data["username"]
return "Login Success"
except Exception:
return "Login Failed"
return "Login Failed"
@app.route('/',methods=['GET'])
def index():
return open(__file__, "r").read()
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5010)
代码解释:
- 导入所需模块:
uuid:用于生成Flask应用程序的随机密钥。Flask、request和session来自flask包:这些模块用于创建Web应用程序、处理HTTP请求和管理用户会话。secret中的black_list:假设black_list包含敏感词汇列表。
- 设置Flask应用程序和密钥:
- 创建一个Flask应用程序对象,并为其设置一个随机生成的密钥,这个密钥用于保护会话数据的安全性。
- 定义一些辅助函数:
check(data): 用于检查传入的数据是否包含在black_list敏感词汇列表中。merge(src, dst): 用于合并两个字典对象,将src字典中的内容合并到dst字典中。
- 定义一个
user类:
- 该类包含了
username和password属性,并有一个check方法,用于检查用户输入的数据是否与当前实例的用户名和密码匹配。
- 创建一个空的
Users列表,用于存储注册的用户信息。- 定义了三个路由(route):
/register:处理用户注册的POST请求。/login:处理用户登录的POST请求。/:处理GET请求,用于显示当前代码文件的内容。
/register路由的处理函数:
- 从POST请求中获取数据,并检查是否包含在敏感词汇列表中。若存在敏感词汇,则注册失败。
- 将接收到的数据转换为JSON格式,并确保数据中包含
username和password字段,否则注册失败。- 创建一个新的
user对象,并将请求中的数据合并到该对象中。- 将该用户对象添加到
Users列表中,完成注册。
/login路由的处理函数:
- 从POST请求中获取数据,并确保数据中包含
username和password字段。- 遍历
Users列表中的用户对象,使用user.check(data)方法检查用户输入的数据是否与任何已注册用户的用户名和密码匹配。- 若匹配成功,则将
username存储到会话(session)中,并返回"Login Success",表示登录成功。
/路由的处理函数:
- 处理GET请求,将当前代码文件的内容返回给请求者。
这道题现学Python原型链污染
具体可看Article_kelp师傅:Python原型链污染变体(prototype-pollution-in-python)
有两种非预期解
第一种,通过file属性直接读取环境变量
__file__是从中加载模块的文件的路径名(如果它是从文件加载的)。__file__对于静态链接到解释器的C模块,该属性不存在。对于从共享库动态加载的扩展模块,它是共享库文件的路径名。
在您的情况下,模块正在__file__全局名称空间中访问其自己的属性。
因为__init__被ban了,所以可以利用全局变量直接使file为存储环境变量的文件
payload:
{
"username":"aaa",
"password":"bbb",
"__class__":{
"check":{
"__globals__":{
"__file__" : "/proc/1/environ"
}
}
}
}

通过/路由可以看出,该路由直接通过__file__属性来读取文件并进行输出,所以直接访问就可以了

第二种,static静态目录污染
_static_url_path这个属性中存放的是
flask中静态目录的值,默认该值为static。访问flask下的资源可以采用如http://domain/static/xxx,这样实际上就相当于访问_static_url_path目录下xxx的文件并将该文件内容作为响应内容返回
所以我们可以直接构造payload来进行污染,题目过滤掉了__init__,但是check之后经历了一次json.loads,而且json识别unicode,所以我们可以通过Unicode编码进行绕过,payload:
{
"__init\u005f_":{
"__globals__":{
"app":{
"_static_folder":"/"
}
}
},
"username":1,
"password":1
}
该payload出自Boogipop师傅:DASCTF 2023 & 0X401 Web WriteUp
构造之后_static_folder的值就变成根目录了

然后可以读取环境变量来得到flag

预期解:
题目是开启了flask的debug模式,访问console控制台,然后配合任意文件读取来计算PIN码,最后进行RCE
利用脚本来计算PIN码
PIN码生成六要素
- username:可以在任意文件读取下读取
/etc/password进行猜测- modname:默认是
flask.app- appname:默认是
Flask- moddir flask库下
app.py的绝对路径,可以通过报错拿到,如传参的时候给个不存在的变量- uuidnode mac地址的十进制:任意文件读取
/sys/class/net/the0/address- machine_id:机器码
关于machine_id
如果能任意文件读尝试去读取/usr/local/lib/python3.7/site-packages/werkzeug/debug/__init__.py

博客对EzFlask、ez_cms、MyPicDisk、ez_py、ez_timing等项目代码进行分析。涉及Flask应用开发、Django项目设置,还探讨了原型链污染、本地文件包含、命令拼接注入等漏洞利用方法,如计算PIN码进行RCE、构造phar包上传等。
最低0.47元/天 解锁文章
318

被折叠的 条评论
为什么被折叠?



