闭包属性漏洞

发布时间:2024年01月19日

闭包属性漏洞

第三方库或者在组件在设计的时候,经常会通过一个立即执行函数,在立即函数里面去定义一个局部变量,然后返回一个可以获取这个局部变量的函数。

这种写法的好处是可以屏蔽外部使用者直接访问到内部的变量,防止外部使用者篡改内部变量引起整个程序发生错误。

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的一个访问函数getisOld函数。正常情况下这段代码能正常运行,并且isOld会一直返回false。但是这样子真的安全吗?我们有没有办法在使用的时候找到这段代码的漏洞直接修改闭包变量obj呢。

valueof

首先想到的是valueOf。我们知道valueOfObject原型上的一个方法,它的作用就是将 this值转换成对象。对于对象就是直接返回这个对象this

const obj = {
 ?name: 'csuxzy',
 ?age: 18,
}
?
obj.valueOf().name = 'hello valueOf'
?
console.log(obj.name) // hello valueOf

例如这段代码,我们通过obj.valueOf().nameobjname重写之后调用obj.name,也确实返回了重写之后。

那么针对上面闭包的场景。valueOfObject原型上的一个方法,我们的obj又是继承至Object。根据属性查找的规则,如果当前对象没找到会一直去原型链上面去找。有了这个知识之后我们就可以尝试通过valueOf的方式进行注入更改。

o.get('valueOf')()

我们传入valueOf作为key,在obj上肯定没有这个属性,就会一直沿着原型链找,直到找到Object原型上的方法,但是很遗憾,运行之后我们会报错:

TypeError: Cannot convert undefined or null to object

这是为啥呢。仔细分析我们可以看出来这和this指向有关。valueOf的简易实现如下:

Object.prototype.valueOf = function () {
 ?return Object(this);
};

其实就是返回this。我们普通调用obj.valueOfthis肯定指向obj。但是上面o.get('valueOf')()这种写法可以改写为:

const obj = {
 ?name: 'csuxzy',
 ?age: 18,
}
const v = obj.valueOf
console.log(v())

这时候this 值并不会自动绑定到 obj 对象上,而是默认绑定到全局对象或者 undefined(在严格模式下)。

所以valueOf的方式行不通,但是最起码我们有这种思路就已经成功了一大半。

增加原型属性

那么既然走this的方式行不通,我们能不能通过属性的方式来访问返回本身呢,这样就不需要this了呀。显然是没有这种属性的。但是上面原型链查找给了我们思路。我们可以在原型上实现这样一个getter属性

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

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了。但是这种方法太过于暴力,会导致原型链上的所有方法我们都访问不了啦。所以有第二种方式。

阻止访问原型链

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上的属性,对于其他属性我们就不管了,这样也不会去原型链上面找,更加的优雅。

总结

可以看到,这一个小小的问题包含了非常多的知识点,在平时写代码或者实现第三方库的时候对于这些空子我们都应该尽量的避免,保证业务的健壮性。

文章来源:https://blog.csdn.net/QD_ANJING/article/details/135698963
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。