原型中毒
文章参考:
A Tale of (prototype) Poisoning
JavaScript Prototype Poisoning Vulnerabilities in the Wild
测试环境:node 11.11.0
let string='{"b":1,"__proto__":{"c":6}}'
const strObj = JSON.parse(string)
console.log(strObj)
// { b: 1, __proto__: { c: 6 } }
console.log(strObj.c)
// undefined
const strObj2 = Object.assign({},strObj)
console.log(strObj2)
// { b: 1 }
console.log(strObj2.c)
// 6
JSON.parse有一个漏洞,在第一次解析字符串时,就会把c加在原型上。使用Object.assign({},strObj)复制JSON.parse返回的对象,就会导致复制的新对象原型上挂载了恶意属性。
漏洞危害:可能存在恶意构造的json字符串,包含__proto__键名,如果某些库采用JSON.parse,会导致原型上挂载一些原来不存在的属性。
一个简单的深度copy函数
const parsedObj = JSON.parse(jsonString);
function copy(oldObj){
const newObj = {}
for (key of Object.keys(oldObj))
{
if(typeof key=='object'){
return copy(key)
} else{
newObj[key]=oldObj[key]
}
}
return newObj
}
JSON.parse返回的对象是不能访问a这个key的,但是任意一个深度copy函数复制后,这时返回的对象就会挂载这个原型了
const jsonString = `{
"__proto__": { "a": 1 },
"b": 2
}`;
const parsedObj = JSON.parse(jsonString);
console.log(parsedObj.__proto__.a) //1
console.log(parsedObj.a) //undefined
console.log(parsedObj) //{ __proto__: { a: 1 }, b: 2 }
let parsedObj_copy = copy(parsedObj)
console.log(parsedObj_copy) // {b:2}
console.log(parsedObj_copy.a) // 1 ,复制后挂载到原型上了,变得可以访问了
console.log(parsedObj_copy.__proto__.a) //1
具体危害方式: 如果前端请求的json来源不可靠,存在被注入的风险。
修复方式: github.com/hapijs/bour…
简易版(参见解决办法js):
function parse(text) {
const obj = JSON.parse(text);
let next = [obj];
while (next.length) {
const nodes = next;
next = [];
for (const node of nodes) {
if (Object.prototype.hasOwnProperty.call(node, '__proto__')) { // Avoid calling node.hasOwnProperty directly
delete node.__proto__;
}
for (const key in node) {
const value = node[key];
if (value &&
typeof value === 'object') {
next.push(node[key]);
}
}
}
}
return obj
};
// 测试一下
const jsonString = `{
"__proto__": { "a": 1 },
"b": 2
}`;
console.log(parse(jsonString))