闭包属性漏洞
第三方库或者在组件在设计的时候,经常会通过一个立即执行函数,在立即函数里面去定义一个局部变量,然后返回一个可以获取这个局部变量的函数。
这种写法的好处是可以屏蔽外部使用者直接访问到内部的变量,防止外部使用者篡改内部变量引起整个程序发生错误。
javascript
var o = (function() {
const obj = {
name: 'csuxzy',
age: 18,
}
return {
get(key) {
return obj[key]
},
isOld() {
return obj.age > 18;
}
}
})()
console.log(o.get('name')); // csuxzy
console.log(o.isOld()); // false
例如这段代码模拟一个第三方库的行为。我们通过闭包定义了一个局部变量obj,然后返回了这个obj的一个访问函数get和isOld函数。正常情况下这段代码能正常运行,并且isOld会一直返回false。但是这样子真的安全吗?我们有没有办法在使用的时候找到这段代码的漏洞直接修改闭包变量obj呢。
valueof
首先想到的是valueOf。我们知道valueOf是Object原型上的一个方法,它的作用就是将 this值转换成对象。对于对象就是直接返回这个对象this。
arduino
const obj = {
name: 'csuxzy',
age: 18,
}
obj.valueOf().name = 'hello valueOf'
console.log(obj.name) // hello valueOf
例如这段代码,我们通过obj.valueOf().name对obj的name重写之后调用obj.name,也确实返回了重写之后。
那么针对上面闭包的场景。valueOf是Object原型上的一个方法,我们的obj又是继承至Object。根据属性查找的规则,如果当前对象没找到会一直去原型链上面去找。有了这个知识之后我们就可以尝试通过valueOf的方式进行注入更改。
csharp
o.get('valueOf')()
我们传入valueOf作为key,在obj上肯定没有这个属性,就会一直沿着原型链找,直到找到Object原型上的方法,但是很遗憾,运行之后我们会报错:
typescript
TypeError: Cannot convert undefined or null to object
这是为啥呢。仔细分析我们可以看出来这和this指向有关。valueOf的简易实现如下:
javascript
Object.prototype.valueOf = function () {
return Object(this);
};
其实就是返回this。我们普通调用obj.valueOf时this肯定指向obj。但是上面o.get('valueOf')()这种写法可以改写为:
arduino
const obj = {
name: 'csuxzy',
age: 18,
}
const v = obj.valueOf
console.log(v())
这时候this 值并不会自动绑定到 obj 对象上,而是默认绑定到全局对象或者 undefined(在严格模式下)。
所以valueOf的方式行不通,但是最起码我们有这种思路就已经成功了一大半。
增加原型属性
那么既然走this的方式行不通,我们能不能通过属性的方式来访问返回本身呢,这样就不需要this了呀。显然是没有这种属性的。但是上面原型链查找给了我们思路。我们可以在原型上实现这样一个getter属性
javascript
var o = (function() {
const obj = {
name: 'csuxzy',
age: 18,
}
return {
get(key) {
return obj[key]
},
isOld() {
return obj.age > 18;
}
}
})()
Object.defineProperty(Object.prototype, 'getObj', {
get() {
return this
}
})
o.get('getObj').name = 'hello world';
o.get('getObj').age = 19;
console.log(o.get('name')); // hello world
console.log(o.isOld()); // true
通过在原型上增加getObj属性,他有一个访问器属性getter。调用getObj就会返回obj本身。那么这里为什么不会有this指向的问题呢。其实我们可以思考下,o.get('getObj')其实就是obj.getObj。所以这个this指向的就是obj。
至此,我们就通过原型链这个漏洞实现了更改闭包里面的属性。所以与其说是闭包的漏洞,不如说是js的漏洞。
防范
如果我们确实有这种需求我们应该怎么防范呢。
设置我们闭包属性的原型为null
javascript
var o = (function() {
const obj = {
name: 'csuxzy',
age: 18,
}
Object.setPrototypeOf(obj, null) // 关键代码
return {
get(key) {
return obj[key]
},
isOld() {
return obj.age > 18;
}
}
})()
Object.defineProperty(Object.prototype, 'getObj', {
get() {
return this
}
})
既然你是通过原型链注入的,那我直接断了原型链不就行了。这样我们通过o.get('getObj')拿到的就是undefined了。但是这种方法太过于暴力,会导致原型链上的所有方法我们都访问不了啦。所以有第二种方式。
阻止访问原型链
javascript
var o = (function() {
const obj = {
name: 'csuxzy',
age: 18,
}
return {
get(key) {
if (obj.hasOwnProperty(key)) // 关键代码
return obj[key]
},
isOld() {
return obj.age > 18;
}
}
})()
Object.defineProperty(Object.prototype, 'getObj', {
get() {
return this
}
})
我们可以通过hasOwnProperty属性来只读去obj上的属性,对于其他属性我们就不管了,这样也不会去原型链上面找,更加的优雅。
总结
可以看到,这一个小小的问题包含了非常多的知识点,在平时写代码或者实现第三方库的时候对于这些空子我们都应该尽量的避免,保证业务的健壮性。