Web安全基础篇——Javascript原型污染链

原型与原型链

原型是Javascript中继承的基础,Javascript的继承就是基于原型的继承

  • 所有引用类型(函数,数组,对象)都拥有__proto__属性(隐式原型)
  • 所有函数拥有prototype属性(显式原型)(仅限函数)

原型链是Javascript的实现的形式,递归继承原型对象的原型,原型链的顶端是Object的原型。

原型链污染原理

.__proto__ 指向类的 prototype,那么修改了实例化的对象的 .__proto__ 的内容,类的 prototype 的内容是否也会发生改变,这就是原型链污染的利用方法。

造成这个漏洞的有一个很重要的前提,这题目的后端是node.js来写的,也就是说后端不是php服务器,不是java服务器,而是JavaScript服务器。

因为是JavaScript语言来处理后端,用JavaScript的那个函数来处理,所以才有这个漏洞产生。

正常情况下,在代码中正常生成的对象,是没有污染的。

1
2
3
4
5
6
7
8
9
baseUser = {a:1}
user = {a:2, b:1, __proto__:{c:3}}

// 这个函数的作用:浅复制一个对象,第一个参数位是对象的内容,后面的参数位是多个对象内容叠加进去,进行复制出一个全新的对象
let newUser = Object.assign({}, baseUser, user)
// 无污染,结果正常
console.log(newUser) // {a: 2, b: 1}
// 无污染,结果正常
console.log(newUser.__proto__) // [Object: null prototype] {}

我们通过post发送过去的 json是字符串,JavaScript需要通过JSON.parse()函数才能把 json字符串转成对象。使用函数把json字符串转成对象,这样的话__proto__才会被当作一个JSON格式的字符串被解析成键值,而不是被解析成了一个属性值。

1
2
3
4
5
6
7
8
baseUser = {a:1}
user = JSON.parse('{"a":2, "b":1, "__proto__":{c:3}}')

let newUser = Object.assign({}, baseUser, user)

console.log(newUser) // {a: 2, b: 1},__proto__是隐藏属性,是不会直接显示的
console.log(newUser.__proto__) // {c: 4}
console.log(newUser.c) // 4 ,已经被污染了,这个属性是一直存在的

Object.assign 可以发现这个方法是可以触发原型链污染的。