闭包属性漏洞

闭包属性漏洞

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

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

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

valueof

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

arduino 复制代码
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的方式进行注入更改。

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

总结

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

相关推荐
_阿南_31 分钟前
项目中新增给AI制定的代码规范
前端·程序员
名字还没想好☜1 小时前
React 实现暗黑模式切换:localStorage 持久化、SSR 首屏闪烁与跟随系统主题
前端·javascript·react.js·ecmascript·react·next.js
自动化监测Learner2 小时前
主流 Web 端地图引擎对比:选型指南与优劣分析
javascript
console.log('npc')2 小时前
Git 冲突与 AI 协助指南
前端·人工智能·git·大模型
爱学堂IT分享4 小时前
Cesium可视化系统实战课程-Cesium教程学习
前端
糖墨夕4 小时前
理解大语言模型:Agent 的“大脑”
前端·agent
Rain的Java大神之路4 小时前
JavaWeb开发如何解决跨域问题
java·前端·后端·nginx·web安全·面试·运维开发
cpolar技术支持5 小时前
浏览器也能跑本地 AI:用 Transformers.js + WebGPU 做一个最小推理 Demo,cpolar 给同事远程体验
前端·ai·cpolar·webgpu·transformers.js
计算机魔术师6 小时前
OpenAI 发布 GPT-6 Astra:多项基准刷新纪录, cybersecurity 能力达 Critical 阈值
前端
xy34536 小时前
Axure9.0中继器遮罩实现方法
前端·ui·html·axure·原型·产品设计