localStorage + getter = Prototype Pollution

오늘은 Prototype Pollution에 대한 이야기를 잠깐 하려고 합니다. 다름이 아니라 @garethheyes가 아래와 같은 내용의 트윗을 올렸었습니다.

정리하면 localStorage 에서 getter를 사용하는 경우, 즉 직접 접근해서 사용하는 경우 Prototype Pollution에 취약할 수 있다 란 이야기죠. 물론 당연한 이야기지만 글로 한번 공유하는게 좋을 것 같아 남겨봅니다.

Prototype Pollution

Prototype Pollution은 Javascript 처리 로직의 문제로 Object 들의 prototype을 수정할 수 있을 때 발생하는 보안 문제를 의미합니다. XSS 관련 문제에서 자주 나오며, 실제로 서비스 사레에서도 종종 나오기도합니다.

공격에 대한 자세한 내용은 Cullinan > Prototype Pollution 문서를 참고해주세요!

const obj2 = JSON.parse('{"__proto__":{"hacked":45}}');
merge(obj1, obj2);
const obj3 = {};
obj3.hacked; // 45

localStorage and getter

localStorage에서 데이터를 꺼내는 방법은 여러가지가 있습니다. 보통 get() 함수를 사용하지만 종종 getter로 호출해서 꺼내는 경우가 있습니다.

// get() 함수 사용 
localStorage.get('testdata')

// getter 사용
localStorage.testdata

Pollution!

localStorage 또한 Object이고 merge나 queryParse 등으로 Pollution이 일어나는 경우 Object 하위 요소를 임의로 변경할 수 있어, 결국 localStorage에 저장된 데이터를 getter로 꺼내는 경우 Pollution된 내용이 먼저 사용되기 떄문에 임의로 값을 변경할 수 있게됩니다.

하나 간단하게 예시를 들어보면 제 Firefox에는 StackColorScheme란 값이 하나 저장되어 있습니다.

예전에 제가 darkmode toggle 테스트한다고 임의로 넣어둔 값이였네요

값은 dark 라는 값이 들어있습니다.

localStorage.StackColorScheme 
// => "dark"

이 때 Prototype Pollution 을 통해 Object의 prototype 중 StackColorScheme 값을 변경했을 때 localStorage.StackColorScheme 와 같이 getter로 값을 꺼내는 경우 pollution 된 값을 읽어오게 됩니다.

Object.prototype.StackColorScheme = "hahwul!" 
localStorage.StackColorScheme
// => "hahwul!" 

실제론 아래와 같은 케이스가 가장 흔하겠네요.

// localStorage.mydebug is false

// Pollution!
const obj2 = JSON.parse('{"__proto__":{"mydebug":true}}');
merge(obj1, obj2);

// localStorage.mydebug is true

if localStorage.mydebug {
  // some debug logic..
}