Prototype pollution

Definition

Inject Property 到一些 JavaScript 現有的物件(像是 Date 物件, 甚至是所有物件的根物件 Object)的 __proto__ 之中,或是直接 Modify __proto__ 現有的 property

About __proto__ and prototype

My note - Object’s prototype

How

直接去 add/modify 物件 __proto__ 的 property

1
2
3
4
5
6
7
8
9
const a = {};
const b = {};
const c = 1;

a.__proto__.a = 2;

console.log(a.a); // 1
console.log(b.a); // 1
console.log(c.a); // 2, 因為 c 的 __proto__ 是 Number, 是一個建構子函數, 函數的 __proto__ 為 Object

透過 JSON.parse() 搭配一些 property merging function 去更動 __proto__

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function merge(target, source) {
for (let key in source) {
if (key in source && key in target) {
merge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
}

const a = {};
const str = `{"a": 1, "__proto__": {"hehe": 1}}`;
const b = JSON.parse(str);

merge(a, b);

const c = {}
console.log(c.hehe) // 1.
  • 為什麼不能

    1
    2
    3
    4
    5
    6
    7
    const a = {};
    const b = { __proto__: { hehe: 1 }};

    merge(a, b);

    const c = {}
    console.log(c.b) // undefined
    • 透過宣告直接指定 __proto__ 會被視為真的是 b 的 __proto__, 也就是直接覆蓋掉了 b 的 __proto__, 而並非 b 身上的 property
    • 但是透過 JSON.parse(), 會直接將 __proto__ 視為 b 身上的 property

透過 JSON.parse() 搭配一些 property merging function 去更動 constructor 中的 prototype

  • 與上述更動 __proto__ 的方式雷同, 只是這次目標是 constructor.prototype
  • constructor.prototype
    • 物件皆有 constructor function, 即時是單純的 Object
    • (constructor function 的 prototype) === (由該 constructor function 建構出來的 instance 的 __proto__)

Prototye pollution in lodash API (目前新版都已被修正)

_.merge()

1
2
var payload = '{"__proto__": {"a": "hehe"}}';
_.merge({}, JSON.parse(payload));

_.defaultsDeep()

1
2
const payload = '{"constructor": {"prototype": {"toString": true}}}'
_.defaultsDeep({}, JSON.parse(payload))

可能造成的 Security problems

Dos

  • 情境
    • 當 server 接收 client 送來的 payload, 進行 JSON.parse() 後 merge 到現有的資料中 (e.g. session data)
    • 可以透過這個 payload 去修改 __proto__ , 竄改 Object 的一些 bulid-in function (e.g. toString()), 讓他拋出錯誤, 之後若在 server code 中有使用到 toString(), 就會造成 error, 進而 crash server

Property Injection

  • 情境
    • 透過 userA.isAdmin flag 來判斷 userA 是否具有 admin 權限
    • 在 Object.prototype 中加入一個 isAdmin 並設為 true, 如此一來任何人(物件)在 prototype chain 上最終都會有 isAdmin 這個 property 且為 true

防範方法

  1. 在做 object merging/cloning 機制時, 需要擋掉 key === ‘__proto__’ 或是 key === ‘constructor’

  2. Freeze the prototype — Object.freeze(Object.prototype)

    • 使其不在能做任何 key 的增減修改

參考資料