웹해킹 7차시 Prototype Pollution

sjong 2023. 6. 7. 01:55

Prototype Pollution

Java, Python 같은 언어처럼 Javascript도 객체지향 언어다. 하지만 객체지향을 구현하는 방법에서 약간의 차이가 있다. 객체지향을 표방한 다른 프로그래밍 언어에서는 ‘class’ 라는 개념을 볼 수 있는데, Javascript에서는 ‘class’라는 개념이 없다. 그래서 Javascript에는 prototype이라는 Javascript 고유 특성을 이용해 상속 기능을 구현했다. 그리고 Javascript의 이러한 특성을 이용한 취약점이 있다.

 

기본적으로 객체 리터럴의 __proto__는 Object.prototype과 같다는 것을 이용한 것인데 Javascript로 어떻게 터지는지 보겠다.

const a = {};
console.log(a.__proto__ == Object.prototype); // true
a.__proto__.polluted = 1;
const b = {};
console.log(b.polluted); // 1

 

먼저 a라는 빈 객체를 생성해 준다.

다음으로 a.__proto__가 Object.prototype과 같다는 것을 확인한다.

그리고 a.__proto__.polluted로 a 객체의 프로토타입을 polluted로 두고, 프로퍼티 값을 1로 찍는다.

그러면 b의 polluted도 1로 출력된다.

 

원래는 defined로 표시되어야 하나, proto를 수정함으로써 Object.prototype에 영향을 끼치고, 1로 표시되는 걸 확인할 수 있다.

 

이것만으론 부족할 수 있기에 다른 코드를 보겠다.

const a = [1, 2, 3];
const b = [4, 5, 6];
a.wow = 1;
b.wow = 1;
console.log(a.wow); // 1
console.log(b.wow); // 1

a와 b 객체는 {"wow":1}이라는 공통적인 요소를 가지고 있다. 또한 a.wow를 수정하더라도 b.wow는 변하지 않는다.

그런데 만약 여기서 prototype을 건드리면?

const a = [1, 2, 3];
const b = [4, 5, 6];
a.__proto__.wow = 1;
console.log(a.wow); // 1;
console.log(b.wow); // 1;

a.__proto__.wow에만 값을 넣었음에도 불구하고 b.wow에 값이 들어간 것을 확인할 수 있다.

또한 생성되는 요소에도 계속 들어가게 된다.

const a = [1, 2, 3];
const b = [4, 5, 6];
a.__proto__.wow = 1;
const c = [7, 8, 9];
console.log(c.wow); // 1

이 외에 다른 방법을 보겠다.

const a = {};
const b = JSON.parse('{"__proto__":{"wow": "wow"}}');
Object.assign(a, b);
console.log(a.wow); // "wow"

 

먼저 a라는 빈 객체를 생성해 준다.

다음으로 b에 JSON 문자열인 {"__proto__":{"wow": "wow"}} 를 파싱에 할당한다.

그러면 b의 프로퍼티에는 "wow"라는 값을 가진 wow 프로퍼티가 추가된다.

그리고 Object.assign함수를 사용한다. Object.assign은 객체를 합쳐주는 역할을 한다.

마지막으로 a.wow 를 출력하면 "wow"가 출력된다.

 

결론적으론 여기까지 전부 다 Object.prototype을 오염시키기 위한 공격이다.

 

대책

 

  • JSON schema : avj 모듈 등을 사용해 JSON을 검증한다.
  • Map : key / value를 저장하는데 객체를 사용하지 않고 Map을 사용한다.
  • Object.freeze : Object.prototype이나 Object를 freeze하여 변경을 불가능하게 하는 방법이다. 부작용으로 정상적인 모듈임에도 이 조치로 동작하지 않을 수도 있다.

Object.freeze의 예시 코드를 보자.

const a = {wow: 1};
Object.freeze(a);
a.wow = 2;
console.log(a.wow); // 1

이렇게 a 객체의 값을 바꾸려고 시도하더라도 Object.freeze 때문에 값이 바뀌지 않는다.

이것을 prototype으로 활용해 보자.

Object.freeze(Object.prototype);
Object.freeze(Object);
const a = {};
a.__proto__.wow = 1;
console.log(a.wow); // defined

Object.prototype과 Object를 냉동시켜 버렸다.

부작용이 가끔 일어나더라도 변경은 안되기에 안전하다.