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)
合并过程,先判断dst目标字典是否为字典形式
是字典形式
- 如果是字典形式,k存在,且值为字典,合并嵌套字典
- 如果是字典形式,k不存在,或值不是字典,将键值对
(k, v)
添加到dst
中
如果dst并不是字典是类
- 类的形式,存在属性k且为字典,合并字典
- 类的形式,不存在属性k,或者k值不是字典,dst将添加k,设置值为v
分析
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)
print(instance.secret)
merge(payload, instance)
print(son_a.secret)
print(instance.secret)
merge(payload, instance)
payload为原字典,instance为目标字典
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__
元组,储存函数或方法的默认参数值,定义函数时,可为其参数指定默认值