目录
前言
什么是原型链
关于prototype这个属性
原型链继承
原型链污染
原型链污染可通过一下几种方式实现
由递归合并造成的原型链污染
merge合并
总结
在2017年,一项名为"Prototype Pollution"的安全漏洞被公开披露,该漏洞利用了JavaScript中的原型继承机制,通过修改原型链上的属性来影响目标对象的行为。这种攻击技术使得攻击者可以篡改或扩展目标对象的原型链,从而导致意外行为或安全漏洞的产生。
原型链污染可能导致严重的安全漏洞和意外行为。攻击者可以通过修改原型对象来添加、修改或删除目标对象上的属性和方法,甚至可能篡改内置对象的原型。这可能导致应用程序中的任意代码执行、信息泄露、拒绝服务等问题。那么什么是原型链,又如何进行污染?
在js语言中,每一个实例对象都有一个私有属性(__proto__)指向它的构造函数的原型对象(prototype)
而该原型对象又有一个自己的原型对象,就像套娃一样。知道原型对象为object,它是几乎所有的javascript中的对象的祖宗。所以它是没有原型的,或者说它的原型为null。到这为止,整条原型链结束。
function Animal(name) {
this.name = name,
this.meow = function () {
console.log('喵喵')
}
}
Animal.prototype.color = 'yellow'
var cat1 = new Animal('大毛')
var cat2 = new Animal('二毛')
console.log(cat1.color) // 'white'
console.log(cat2.color) // 'white'
console.log(cat1.meow === cat2.meow) //false
上面代码中,cat1和cat2是同一个构造函数的两个实例,它们都具有meow方法。由于meow方法是生成在每个实例对象上面,所以两个实例就生成了两次。也就是说,每新建一个实例,就会新建一个meow方法。这既没有必要,又浪费系统资源,因为所有meow方法都是同样的行为,完全应该共享。而prototype属性解决了这个问题,上面代码中我们使用Animal构造函数的prototype属性,将Animal的原型对象添加了color属性,原型对象的color属性的值变为yellow,两个实例对象的color属性立刻跟着变了。这是因为实例对象其实没有color属性,都是读取原型对象的color属性。也就是说,当实例对象本身没有某个属性或方法的时候,它会到原型对象去寻找该属性或方法。这就是原型对象的特殊之处。但如果实例对象自身就有某个属性或方法,它就不会再去原型对象寻找这个属性或方法。
实例对象和原型对象是有继承关系的。当试图访问一个对象的属性时,它会先在该对象中搜索,如果该对象没有此属性,就会在该对象的原型中去搜索。以此类推,如果直到object原型对象都没有这个属性,就会返回undefined。来通过实例说一下:
var ad = function(){
this.a = 1;
this.b = 2;
}
var we = new ad(); //从一个函数里创建一个对象we
ad.prototype.b = 3;
ad.prototype.c = 4;//在ad函数的原型对象中定义属性
console.log(we.a);//we对象中有a属性,为一。
console.log(we.b);//we对象也有b属性,为2.
//原型中也有b属性,但是不会被访问到。也想当于重写。
console.log(we.c);//we对象没有c属性,所以在原型中找,为4
console.log(we.d);//d不是we对象的属性,继续看,d也不是
//we.[[Prototype]]中的属性,继续,d也不是we.[[Prototype]].[[Prototype]]
//中的属性,到此结束,返回undefined
原型链污染主要是攻击者通过修改原型链上的对象来改变应用程序的行为,或者利用原型链上的对象来执行恶意代码。
1、修改Object.prototype或其他原型对象:攻击者可以直接修改Object.prototype或其他原型对象,添加或修改属性和方法。这样,所有继承自该原型的对象都会受到影响。
2、修改Object.prototype.constructor:攻击者可以修改Object.prototype.constructor,将其指向恶意代码或其他构造函数。这样,通过原型链创建的对象在调用构造函数时可能会执行攻击者指定的恶意代码。
3、使用__proto__属性:攻击者可以利用__proto__属性(非标准的属性)来修改对象的原型链。通过修改__proto__属性,攻击者可以将对象的原型链指向任意对象,从而影响对象的属性和方法的继承。
merge函数是一种常见的用于合并对象的函数。它将源对象的属性合并到目标对象中,可以用于深度合并两个对象的属性。
以下是一个示例的merge函数实现:
function merge(target, source) {
for (let key in source) {
if (source.hasOwnProperty(key)) {
if (typeof source[key] === 'object' && typeof target[key] === 'object') {
merge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
}
return target;
}
这个merge函数接受两个参数:目标对象(target) 和源对象(source)。它遍历源对象的属性,并将每个属性合并到目标对象中。
如果属性的值是对象,并且目标对象中相应的属性也是对象,则递归调用merge函数来深度合并这两个对象。
如果属性的值不是对象,直接将源对象的属性值赋给目标对象的相应属性。
最后,返回合并后的目标对象。
通过调用merge函数,可以将一个或多个源对象的属性合并到目标对象中,实现对象属性的合并和覆盖
为了防止原型链污染,开发者应该遵循以下最佳实践:
避免直接修改Object.prototype或其他原型对象,以及它们的属性和方法。
使用Object.create(null)创建纯净的对象,不继承任何原型对象。
谨慎处理不可信数据,避免将其用作原型链的一部分。
使用Object.freeze()或其他方法冻结对象,防止其被修改。
定期更新和审查第三方库,确保其不会引入原型链污染的安全问题。
通过遵循安全原则和最佳实践,可以减少原型链污染的风险,并保护应用程序的安全性和稳定性。