nodejs原型链污染

0x00 前言

JavaScript是一门灵活的语言,比php语言灵活,除了SQL注入,代码执行漏洞外,还有其他类型的安全问题,比如下面要说的nodejs原型链污染。

0x01 JavaScript 原型链与继承

原型链与继承的介绍可以参考:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Inheritance_and_the_prototype_chain#%E4%BD%BF%E7%94%A8%E4%B8%8D%E5%90%8C%E7%9A%84%E6%96%B9%E6%B3%95%E6%9D%A5%E5%88%9B%E5%BB%BA%E5%AF%B9%E8%B1%A1%E5%92%8C%E7%94%9F%E6%88%90%E5%8E%9F%E5%9E%8B%E9%93%BE

在JavaScript中,没有引入类这个概念,通常是定义一个函数然后用new来创建实例。而JavaScript中的继承关系是靠一种叫做“原型链”的模式来实现的。

首先要了解是什么原型链与继承,含义是什么:

原型链是JavaScript中一种重要的机制,用于实现对象之间的继承。理解原型链的关键在于理解JavaScript对象的工作原理及其如何通过原型链查找属性和方法。

原型链的基本概念

对象和原型:

  • 在JavaScript中,每个对象都有一个内部属性 [[Prototype]],这个属性通常指向另一个对象,称为该对象的原型。
  • 这个原型对象本身也有自己的原型,如此类推,形成了一条原型链。
  • 原型链的终点是 null,因为 null 没有原型。

属性查找机制:

  • 当你尝试访问一个对象的属性时,JavaScript引擎会先检查该对象自身是否有这个属性。
  • 如果没有找到,JavaScript引擎会沿着原型链向上查找,直到找到该属性或到达原型链的末端(即 null)。
  • 如果在整个原型链中都没有找到该属性,则返回 undefined

下面让我们举例说明:

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 定义一个构造函数 Person
function Person(name, age) {
this.name = name;
this.age = age;
}

// 在 Person 的原型上添加一个方法
Person.prototype.sayHello = function() {
console.log("你好,我是" + this.name);
};

// 创建一个 Person 实例
let person1 = new Person("李四", 25);

// 调用 sayHello 方法
person1.sayHello(); // 输出:"你好,我是李四"

在这个例子中(其中[[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

  1. JavaScript引擎首先检查 person1 对象自身是否有 name 属性。
  2. 发现 person1 对象自身有 name 属性,直接返回其值 "李四"

假设我们访问 person1.toString

  1. JavaScript引擎首先检查 person1 对象自身是否有 toString 属性。
  2. 没有找到 toString 属性,继续沿原型链向上查找。
  3. Person.prototype 上也没有找到 toString 属性。
  4. 继续向上查找,在 Object.prototype 上找到了 toString 方法。
  5. 返回并执行 Object.prototype.toString 方法。

继承

1
2
3
4
5
6
7
当我们谈到继承时,JavaScript 只有一种结构:对象。每个函数对象有 prototype 属性,而实例对象没有,但所有的实例对象(函数,数组,对象)都会初始化一个私有属性 __proto__ 指向它的 构造函数的原型对象 prototype。该原型对象也有一个自己的原型对象 __proto__,层层向上直到一个对象的原型对象为 null。根据定义,null 没有原型,并作为这个原型链中的最后一个环节。下面我们把构造函数、原型对象、以及实例对象的关系理清如下:

每个构造函数都有一个 prototype 原型对象
每个实例对象都有一个 __proto__ 属性,并且指向它的构造函数的原型对象 prototype
对象里的 constructor 属性指向其构造函数本身

摘自文章:https://xz.aliyun.com/t/10032?u_atoken=3404da9bcd2862aae65d7f800547ea63&u_asig=1a0c39a017319322914792451e0032&time__1311=mqjxRi0QiQoiwx05DIB4CTRQvHDtTedtdF4D

0x02 JavaScript原型链污染漏洞原理

首先打开浏览器的控制台,F12打开控制台,输入:

1
2
3
4
5
6
7
8
9
10
function Person(name) {
this.name = name;
}

Person.prototype.greet = function() {
console.log(`Hello, my name is ${this.name}`);
};

const person1 = new Person('Alice');
person1.greet(); // 输出 "Hello, my name is Alice"

在这个例子中,我们创建了一个名为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
2
3
4
prototype是一个类的属性,所有类对象在实例化的时候将会拥有prototype中的属性和方法
一个对象的__proto__属性,指向这个对象所在的类的prototype属性
该总结摘自:
https://www.leavesongs.com/PENETRATION/javascript-prototype-pollution-attack.html#0x02-javascript

下面举个例子:

1
2
3
4
5
var a = {number : 520}
var b = {number : 1314}
b.__proto__.number=520
var c= {}
c.number

为什么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)

摘自:https://www.freebuf.com/articles/web/361333.html