Prototype Pollution에 대해서 알아봅시다.
Background
Javascript의 객체 지향
- Javascript는 객체 지향 언어
객체지향언어의 class 개념이 javascript에는 존재하지 않음
→ 상속 기능을 사용하지 못한다는 의미prototype이라는 고유 특성을 이용해 상속 기능
을 구현- ECMA6 표준에서 class 키워드가 추가되었지만 궁극적으로 javascript에서 class기반의 객체지향 언어로 바뀌지는 않음
1
2
3
4
5
6
7
let user = {
name: ‘scyoon’,
age: 20,
}
console.log(user.hasOwnProperty(‘name’)); // true
[참고] https://poiemaweb.com/js-prototype
user라는 객체에서 hasOwnProperty() 메소드를 선언하지 않았음에도 호출할 수 있는 이유는 user 객체의 부모 객체에서 hasOwnProperty() 메소드를 상속
받았기 때문입니다.
Obeject literal 객체 선언 방식은 내부적으로 new Object();
가 실행 되며 선언되므로 object 객체를 상속받게됩니다.
Javascript Prototype Chain
- Javascript에서 객체의 부모는
__proto__
로 접근이 가능함 - 자식 객체에서 변수를 찾을 수 없으면 부모 객체에서 해당 변수를 찾게 되는데, Javascript에서는
Prototype Chain
이라고 부름
Prototype
- Javascript에는 객체라는 하나의 구조만 존재
- 각 객체에는 Prototype이라는 다른 객체에 대한 링크를 보유하는 비공개 속성
- Prototype 객체도 자신만의 Prototype을 가지고 있음
- Prototype으로 null을 가진 객체에 도달할 때 까지 연결이 유지됨,
- null에는 prototype이 없으며, prototype chain에서 최종 링크 역할을 함
Prototype Pollution
기본적으로 Object literal(객체 리터럴)의 __proto__
== Object.prototype
과 같다는 것을 이용해 다른 객체의 속성에 영향을 줄 수 있습니다.
prototype chain으로 특정 로직을 우회하거나 공격자가 원하는 방향으로 실행되도록 만드는 공격 기법입니다.
Object literal
literal이란 사람이 이해할 수 있는 문자나 약속된 기호를 사용해 값을 생성하는 표기법이며, JS에서 객체를 생성하는 가장 일반적인 방법이 객체 리터럴을 사용합니다.
객체 리터럴은 {}
내에 0개 이상의 property(객체의 상태를 나타내는 값)를 정의합니다.
Prototype Pollution Pattern
Prototype Pollution 패턴에는 보통 세 가지 패턴(속성 설정, 객체 병합, 객체 복사)이 존재합니다.
Setting Property
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function isObject(obj) {
return obj !== null && typeof obj === 'object';
}
function setValue(obj, key, value) {
const keylist = key.split('.');
const e = keylist.shift();
if (keylist.length > 0) {
if (!isObject(obj[e])) obj[e] = {};
setValue(obj[e], keylist.join('.'), value);
} else {
obj[key] = value;
return obj;
}
}
const obj1 = {};
setValue(obj1, "__proto__.polluted", 1);
const obj2 = {};
console.log(obj2.polluted); // 1
Object Merge
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function merge(a, b) {
for (let key in b) {
if (isObject(a[key]) && isObject(b[key])) {
merge(a[key], b[key]);
} else {
a[key] = b[key];
}
}
return a;
}
const obj1 = {a: 1, b:2};
const obj2 = JSON.parse('{"__proto__":{"polluted":1}}');
merge(obj1, obj2);
const obj3 = {};
console.log(obj3.polluted); // 1
Object Copy
1
2
3
4
5
6
7
8
function clone(obj) {
return merge({}, obj);
}
const obj1 = JSON.parse('{"__proto__":{"polluted":1}}');
const obj2 = clone(obj1);
const obj3 = {};
console.log(obj3.polluted); // 1
Mitigation
이 공격을 방지하는 대책으로는 3가지 방법이 있습니다.
- Object.freeze :
Object.prototype
이나Object
를freeze
하여 변경을 불가능하게 하는 방법.
–> 부작용으로 정상적인 모듈임에도 이 조치로 동작하지 않을 수 도 있습니다. - JSON schema : avj 모듈 등을 사용해 JSON을 검증
- Map : key/value를 저장하는 객체를 사용하지 않고
Map
을 사용. ES5 이전 환경에서는 불가
Reference
https://ufo.stealien.com/2020-12-23/javascript-prototype-pollution
https://velog.io/@ehgks0000/Prototype-Pollution