原型中毒(Prototype Poisoning)

1,240 阅读1分钟

原型中毒

文章参考:

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))