Javascript 原型链
JavaScript 常被描述为一种基于原型的语言 (prototype-based language)——每个对象拥有一个原型对象,对象以其原型为模板、从原型继承方法和属性。原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链 (prototype chain)
当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾
__proto__
和 prototype
在 JavaScript 中,每个对象都有一个名为 __proto__
的内置属性,它指向该对象的原型。同时,每个函数也都有一个名为 prototype
的属性,它是一个对象,包含构造函数的原型对象应该具有的属性和方法。简单来说,__proto__
属性是指向该对象的原型,而
prototype
属性是用于创建该对象的构造函数的原型
1 | // 使用一个构造函数来创建对象 |
从上面的代码可以看出,prototype
是类 Person
的一个属性,所有用类 Person
进行实例化的对象,都会拥有 prototype
的全部内容
实例化出来的 person1
对象,不能通过 prototype
访问原型的,但通过 __proto__
就可以实现访问 Person
原型,如下:
1 | console.log(person1.__proto__ === Person.prototype); |
Javascript 原型链污染
在 JavaScript 中,每个对象都有一个原型,它是一个指向另一个对象的引用。当我们访问一个对象的属性时,如果该对象没有这个属性, JavaScript 引擎会在它的原型对象中查找这个属性。这个过程会一直持续,直到找到该属性或者到达原型链的末尾。攻击者可以利用这个特性,通过修改一个对象的原型链,来污染程序的行为。例如,攻击者可以在一个对象的原型链上设置一个恶意的属性或方法,当程序在后续的执行中访问该属性或方法时,就会执行攻击者的恶意代码
如下面代码所示:
1 | var a = {number : 520} |
上面代码中,c 对象虽然为空,但由于 Javascript 继承链的机制会使它继续递归寻找,也就是从 c.__proto__
中寻找 number
属性,而 c.__proto__
指向了 Object.protoype
,而我们进行污染的 b.__proto__
也是 Object.prototype
,所以 c.number
的值是 520
原型链条污染的发生主要有两种场景:不安全的对象递归合并和按路径定义属性
不安全的对象递归合并
很多开发者会自定义一些递归合并函数,不安全的递归合并函数会导致原型链污染
1 | const merge = (target, source) => { |
上面代码中,job 对象是由用户输入的,并且用户可以输入任意对象。那么我们输入一个含有 proto
属性的对象,那合并的时候就可以把 person
的原型给修改
按路径定义属性
有些 JavaScript 库的函数支持根据指定的路径修改或定义对象的属性值。通常这些函数类似以下的形式:theFunction(object, path, value)
,将对象 object
的指定路径 path
上的属性值修改为 value
如果攻击者可以控制路径 path
的值,那么将路径设置为 _proto_.theValue
,运行 theFunction
函数之后就有可能将 theValue
属性注入到 object
的原型中。
Python 原型链污染
Python 原型链污染既可以实现对类属性的污染,也可以做到对全局变量的属性污染
合并函数
和 JS 一样,同样需要一个数值合并函数将特定值污染到类的属性中,如下:
1 | def merge(src, dst): |
污染自定义属性
python 中的类会继承父类的属性,通过 __base__
属性查找其继承的父类进行污染
1 | class father: |
污染内置属性
通过 __base__
属性查找其继承的父类进行污染
1 | class father: |
如果目标类与切入点类没有继承关系时,这种方法就无法使用
全局变量获取
Python 中,函数、类方法均具有一个 __globals__
属性,该属性包含了当前变量空间的全局变量的字典
对于类的内置方法来说,内置方法在未重写时,其类型为装饰器(
wrapper_descriptor
),只有重写后类型才变成函数(function
)
1 | secret_var = 114 |
因此,可以使用 __globals__
获取到全局变量,从而达到修改无继承关系的类属性甚至全局变量的目的
如下所示:
1 | secret_var = 114 |
获取简单加载模块
如果操作的位置在入口文件,那么也可以对加载过模块中的变量或类属性进行污染
1 | #test.py |
获取复杂加载模块
此外可以利用 sys
模块来进行操作
sys
模块的modules
属性以字典形式包含了程序自运行时所有以加载过的模块,可以直接从该属性中获取目标模块
1 | #test.py |
flask 相关属性污染
secret_key
1 | #app.py |
通过如下 payload 进行污染:
1 | { |
_static_url_path
_static_url_path
这个属性中存放的是 flask 中静态目录的值,默认该值为 static
。访问 flask 下的资源可以采用如 http://domain/static/xxx ,这样实际上就相当于访问 _static_url_path
目录下 xxx 的文件并将该文件内容作为响应内容返回
可以污染该属性改变静态目录的路径,从而访问其它目录下的文件,payload 如下:
1 | { |
然后直接访问静态目录即可,如访问环境变量 http://127.0.0.1/static/proc/1/environ