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)
合并过程,先判断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__元组,储存函数或方法的默认参数值,定义函数时,可为其参数指定默认值
