JavaScript原型链污染:从"西门庆"到"武大郎"的奇妙转变 代码解析
代码解析
让我们先来看这段有趣的JavaScript代码:
let o = (function () {
let obj = {
name: '西门庆',
age: 18
}
return {
get(key) {
return obj[key]
},
}
})();
const { get } = o;
// 通过修改原型链来影响 obj
Object.defineProperty(Object.prototype, 'proObj', {
get() {
return this;
}
});
let obj = o.get('proObj')
obj.name = '武大郎'
console.log(get('name')); // 现在输出 '武大郎' 而不是 '西门庆'
console.log(o.get('proObj'))
代码执行流程分析
1.创建闭包对象:
-
使用立即执行函数(IIFE)创建一个闭包,内部包含一个私有对象obj,初始值为{name: ‘西门庆’, age: 18}
-
返回一个包含get方法的对象,该方法可以访问私有obj
2.解构赋值:
- 从对象o中解构出get方法
3.原型链污染:
-
关键步骤:通过Object.defineProperty在Object.prototype上定义了一个名为proObj的访问器属性
-
这个属性的getter返回this,即访问该属性时的对象本身
4.利用原型链获取私有对象:
-
调用o.get(‘proObj’)时,首先在私有obj上查找proObj属性
-
当私有obj上没有这个属性时,会沿着原型链向上查找
-
在Object.prototype上找到了proObj属性,触发getter,返回了私有obj本身
5.修改私有对象:
-
现在可以通过返回的引用直接修改闭包内的私有对象
-
将name属性从"西门庆"改为"武大郎"
6.验证修改:
-
再次通过get方法获取name属性,发现已变为"武大郎"
-
通过o.get(‘proObj’)可以直接获取到私有obj的引用
技术原理
这段代码展示了JavaScript中几个重要的概念:
1.原型链(Prototype Chain):
-
JavaScript对象有一个隐藏的[[Prototype]]属性,指向它的原型对象
-
当访问对象属性时,如果对象自身没有该属性,会沿着原型链向上查找
2.闭包(Closure):
-
IIFE创建了一个闭包,使得内部变量obj对外不可见
-
正常情况下,外部代码无法直接访问或修改这个私有变量
3.原型污染(Prototype Pollution):
-
通过修改Object.prototype,我们实际上污染了所有对象
-
这是一种潜在的安全风险,可能被恶意利用
4.访问器属性(Accessor Property):
-
使用Object.defineProperty定义的proObj是一个访问器属性
-
它的getter函数返回this,即访问该属性的对象本身
实际应用中的防护
为了防止这类原型链污染攻击,可以采取以下措施:
1.使用Object.hasOwnProperty检查属性:
get(key) {
if (obj.hasOwnProperty(key)) {
return obj[key];
}
return undefined;
}
2.创建无原型的对象:
let obj = Object.create(null);
obj.name = '西门庆';
obj.age = 18;
3.冻结或密封对象:
Object.freeze(obj); // 或 Object.seal(obj);
这段代码巧妙地利用了JavaScript的原型链机制,突破了闭包的封装,实现了对私有变量的访问和修改。它不仅展示了JavaScript语言的灵活性,也提醒开发者注意潜在的安全风险。在实际开发中,我们应该谨慎处理对象属性访问,特别是当属性名来自不可信来源时。
通过这个"西门庆"变"武大郎"的例子,我们可以更深入地理解JavaScript的原型链和闭包机制,以及它们可能带来的安全问题。