JavaScript原型链污染

刷一个新生赛遇到的知识点,既然以后也要学,那不如现在就先好好了解一下

JavaScript原型

JavaScript 常被描述为一种基于原型的语言 (prototype-based language)——每个对象拥有一个原型对象,对象以其原型为模板、从原型继承方法和属性。原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链 (prototype chain)。

JavaScript中,我们如果要定义一个类,需要以定义“构造函数”的方式来定义。每个函数都有一个特殊的属性叫作原型(prototype)

js创建对象的三种方法 :

普通创建

1
2
3
var person={name:'lihuaiqiu','age','19'}

var person={} //创建空对象

构造函数方法创建

1
2
3
4
5
6
7
8
9
10
11
function person(){
this.name="liahuqiu";
this.test=function () {
return 23333;

}
}
person.prototype.a=3;
web=new person();
console.log(web.test());
console.log(web.a)

通过object创建

1
2
3
var a=new Object();
a.c=3
console.log(a.c)

函数即对象(这里的函数有点类似于一个类)
new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。

constructor理解

Object 实例的 constructor 数据属性返回一个引用,指向创建该实例对象的构造函数。注意,此属性的值是对函数本身的引用,而不是一个包含函数名称的字符串。

描述

除了 null 原型对象之外,任何对象都会在其 [[Prototype]] 上有一个 constructor 属性。使用字面量创建的对象也会有一个指向该对象构造函数类型的 constructor 属性。

举例

1
2
3
function Person(){}
const person1 = new Person()
const person2 = new Person()

下面这张图就反映了他们constructor的指向(忽略__proto__和prototype

e95af566d41142c786acc3de7c472b7c~tplv-k3u1fbpfcp-zoom-in-crop-mark_1512_0_0_0

图中,蓝色底是Person的实例对象,而PersonsFunction的函数(也是对象)
首先,我们已经知道每个对象都可以通过对象.constructor找到自己的构造函数,我们先假设每个对象上面都有constructor属性,然后理解:

注意constructor属性不一定是对象本身的属性,这里只是方便理解。第三点会详细讲

  1. person1 , person2都是Person的实例,它们的constructor都指向它们的构造函数Person函数
  2. Person是函数,但同时他也是Function实例对象,它的constructor指向创建它的构造函数即Function函数
  3. Function函数为js的内置对象,它的构造函数是它本身,所以它的constuctor指向它本身

所以constructor属性其实就是拿来保存自己构造函数引用的属性,没有特殊地方

prototype和proto用法

_proto_ :是实例对象指向原型对象的指针,隐式原型,是每个对象都会有的一个属性。
prototype:是构造函数的原型对象,显式原型,只有函数才会有。

对象.proto=构造器(构造函数).prototype

构造器.prototype其实也是一个对象,为构造函数的原型对象,同样有__proto__属性,一直通过原型链__proto__最终可找到null。

我们可以通过Foo.prototype来访问Foo类的原型,但Foo实例化出来的对象,是不能通过prototype访问原型的。这时候,就该__proto__登场了。

一个Foo类实例化出来的foo对象,可以通过foo.__proto__属性来访问Foo类的原型,也就是说:

foo.proto == Foo.prototype
prototype是一个类的属性,所有类对象在实例化的时候将会拥有prototype中的属性和方法 一个对象(foo)的__proto__属性,指向这个对象所在的类(Foo)的prototype属性

Constructor.prototype 默认具有一个自有属性:constructor,它引用了构造函数本身。即,Box.prototype.constructor === Box。这允许我们在任何实例中访问原始构造函数。

继承“方法”

JavaScript 并没有其他基于类的语言所定义的“方法”。在 JavaScript 中,任何函数都被可以添加到对象上作为其属性。函数的继承与其他属性的继承没有差别,包括上面的“属性遮蔽”(这种情况相当于其他语言的方法重写)。

aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2Jsb2cvODUwMzc1LzIwMTkwNy84NTAzNzUtMjAxOTA3MDgxNTMxMzk1NzctMjEwNTY1MjU1NC5wbmc

原型链污染

在JavaScript发展历史上,很少有真正的私有属性,类的所有属性都允许被公开的访问和修改,包括proto,构造函数和原型。攻击者可以通过注入其他值来覆盖或污染这些proto,构造函数和原型属性。然后,所有继承了被污染原型的对象都会受到影响。原型链污染通常会导致拒绝服务、篡改程序执行流程、导致远程执行代码等漏洞。
原型链污染的发生主要有两种场景:不安全的对象递归合并和按路径定义属性。

漏洞利用

由 JavaScript 的原型链查找建立起来的秩序使得构造器和实例处于一种稳定的关系之中,虽然我们通过Object.defineProperty(obj, '__proto__', { value })的方式改变一个对象的__proto__属性并不能改变这个对象的原型,但是我们却能够通过__proto__访问到它的原型

如果我们对它的原型进行一些修改,就能达到改变程序逻辑的目的,从而触发一些漏洞,这种人为通过原型链修改原型的行为成为原型链污染,是一种常见的攻击手段

对于大多数非 JavaScript 预定义的对象,可以通过__proto__或者constructor.prototype访问它的原型,对它原型下的属性进行修改可以造成原型链污染