下面这段代码可以实现:通过立即执行函数o返回对象中的get方法,通过参数key得到fn函数内部对象obj中的值。
var fn = function () {
var obj = {
a: 1,
b: 2
}
return {
get: function (key) {
return obj[key]
}
}
}()
console.log(fn.get('b')); // 2
这是一个典型的闭包场景,这种做法可以做到屏蔽fn内部的obj,不可在外部直接访问obj,只能通过返回的get方法读取数据。
这是很多第三方库的做法,但是通过漏洞,也可以做到直接修改fn函数内部obj的值。
上述写法中,get方法的权限过大,除了obj自身的属性a和b之外,还可以访问到它的原型成员。
方式一:valueOf
初步想法可以通过原型上的 valueOf 方法,获取obj本身。
尝试获取valueOf方法:
console.log(fn.get('valueOf'));
可以正常访问到valueOf方法,但是执行后发现报错:
console.log(fn.get('valueOf')());
这是由于valueOf语法为:obj.valueOf(),上述代码执行后等于直接调用valueOf(),所以该方法不适合上述场景。
但是如果get方法返回的不是一个值,而是函数调用的话,那么该方法就可以正常执行了。具体如下:
var fn = function () {
var obj = {
a: ()=>{return 1},
b: ()=>{return 2}
}
return {
get: function (key) {
return obj[key]()
}
}
}()
let o = fn.get('valueOf')
console.log(o) // 正常获取
console.log(fn.get('a')) // 1
篡改obj数据
o.a = () => {return "abcdefg"}
o.b = () => {return "1234567"}
console.log(fn.get('a')) // 'abcdefg'
console.log(fn.get('b'))// '1234567'
方式二:Object.defineProperty
当一个属性为访问器时,读取这个属性,就会变成函数调用。
在原型上随意添加一个属性
Object.defineProperty(Object.prototype,'qwe',{
get(){
return this
}
})
此时通过fn中的get函数读取原型上的属性qwe时,就会触发get函数,return的this就是fn中的obj。
后续可以对这个对象进行篡改
let o = fn.get('qwe')
o.a = "123"
o.b = "456"
console.log(fn.get('a')) // '123'
console.log(fn.get('b')) // '456'
如何处理这个漏洞
如果封装一些公共模块儿时,一定要注意处理这个漏洞,避免数据被污染或恶意篡改。
解决方法
一、检查参数
get的参数key,要添加检查,必须为obj本身的属性,不能是原型上的。
此时需要使用一个Object的静态方法:Object.hasOwnProperty。
var fn = function () {
var obj = {
a: 1,
b: 2
}
return {
get: function (key) {
if(obj.hasOwnProperty(key)){
return obj[key]
}
return undefined
}
}
}()
再次尝试获取在原型上添加的qwe属性,发现无法获取。
fn.get('qwe')
二、将fn内的obj原型设为空
使用Object.setPrototypeOf(),将obj的原型设置为空。
var fn = function () {
var obj = {
a: 1,
b: 2
}
Object.setPrototypeOf(obj,null)
return {
get: function (key) {
return obj[key]
}
}
}()
尝试获取obj,由于原型为null,所以返回undefined。
fn.get('qwe')
更多Object的静态方法可以看另一篇文章:Object的静态方法