nodejs原型链污染
nodejs原型链污染
Kn1ght0x00 前言
JavaScript是一门灵活的语言,比php语言灵活,除了SQL注入,代码执行漏洞外,还有其他类型的安全问题,比如下面要说的nodejs原型链污染。
0x01 JavaScript 原型链与继承
在JavaScript中,没有引入类这个概念,通常是定义一个函数然后用new
来创建实例。而JavaScript中的继承关系是靠一种叫做“原型链”的模式来实现的。
首先要了解是什么原型链与继承,含义是什么:
原型链是JavaScript中一种重要的机制,用于实现对象之间的继承。理解原型链的关键在于理解JavaScript对象的工作原理及其如何通过原型链查找属性和方法。
原型链的基本概念
对象和原型:
- 在JavaScript中,每个对象都有一个内部属性
[[Prototype]]
,这个属性通常指向另一个对象,称为该对象的原型。 - 这个原型对象本身也有自己的原型,如此类推,形成了一条原型链。
- 原型链的终点是
null
,因为null
没有原型。
属性查找机制:
- 当你尝试访问一个对象的属性时,JavaScript引擎会先检查该对象自身是否有这个属性。
- 如果没有找到,JavaScript引擎会沿着原型链向上查找,直到找到该属性或到达原型链的末端(即
null
)。 - 如果在整个原型链中都没有找到该属性,则返回
undefined
。
下面让我们举例说明:
示例:
1 | // 定义一个构造函数 Person |
在这个例子中(其中[[Prototype]]指的是内部属性):
person1
是一个Person
实例。person1
的内部属性[[Prototype]]
指向Person.prototype
。Person.prototype
自身的[[Prototype]]
指向Object.prototype
。Object.prototype
的[[Prototype]]
指向null
。
原型链示意:
1 | person1 -> Person.prototype -> Object.prototype -> null |
其中 ->
可以理解为 原型指向
属性查找过程:
假设我们访问 person1.name
:
- JavaScript引擎首先检查
person1
对象自身是否有name
属性。 - 发现
person1
对象自身有name
属性,直接返回其值"李四"
。
假设我们访问 person1.toString
:
- JavaScript引擎首先检查
person1
对象自身是否有toString
属性。 - 没有找到
toString
属性,继续沿原型链向上查找。 - 在
Person.prototype
上也没有找到toString
属性。 - 继续向上查找,在
Object.prototype
上找到了toString
方法。 - 返回并执行
Object.prototype.toString
方法。
继承
1 | 当我们谈到继承时,JavaScript 只有一种结构:对象。每个函数对象有 prototype 属性,而实例对象没有,但所有的实例对象(函数,数组,对象)都会初始化一个私有属性 __proto__ 指向它的 构造函数的原型对象 prototype。该原型对象也有一个自己的原型对象 __proto__,层层向上直到一个对象的原型对象为 null。根据定义,null 没有原型,并作为这个原型链中的最后一个环节。下面我们把构造函数、原型对象、以及实例对象的关系理清如下: |
0x02 JavaScript原型链污染漏洞原理
首先打开浏览器的控制台,F12打开控制台,输入:
1 | function Person(name) { |
在这个例子中,我们创建了一个名为Person的构造函数,并设定一个greet函数,使用new创建一个新的实例,就会继承Person.prototype对象上的greet方法,所以,当我们调用person.greet时,他会输出
Hello, my name is Alice
person1是我们实例化出来的对象,不能通过prototype访问原型,但是可以通过_proto_访问原型
代码如下:
1 | console.log(person1.__proto__ === Person.prototype); // 输出 true |
总结:
1 | prototype是一个类的属性,所有类对象在实例化的时候将会拥有prototype中的属性和方法 |
下面举个例子:
1 | var a = {number : 520} |
为什么c.number是520:
虽然c是空的,但是JavaScript继承机制让他递归寻找到了c._proto_中的number属性,原型链刚刚被我们污染了,c._proto_就是Object.prototype,所以调用c.number就是找到了我们刚刚污染的属性,所以最后c.number=520
0x03 JS大小写特性
对于toUpperCase()
函数
1 | 字符"ı"、"ſ" 经过toUpperCase处理后结果为 "I"、"S" |
对于toLowerCase
1 | 字符"K"经过toLowerCase处理后结果为"k"(这个K不是K) |