G2H

보안 리서치 · 레드팀/블루팀 · DFIR · Cloud · Tooling

최신 글 보기

최근에 작성된 글들을 확인해보세요.

prototype pollution

Web/Web Hacking Techniques

prototype 이란?

JavaScreipt는 prototype 기반 언어라 불리며, 프로토타입이란 javascript에서 객체를 상속하기 위해 사용되는 방식을 의미한다.

 

Javascript의 함수는 기본적으로 객체이며, 모든 함수는 자동으로 prototype이라는 속성을 가지고 있다.

이 때 새로운 객체를 만들 때 객체의 prototype 역할을 하게 된다.

즉, prototype 객체는 상위 prototype 객체로부터 메소드와 속성을 상속 받을 수 있고, 그 상위 prototype 객체도 마찬가지이며, 이를 프로토타입 체인이라고 부른다.

function Person(first, last, age, gender, interests) {
}

var person1 = new Person("Tammi", "Smith", 32, "neutral", [
  "music",
  "skiing",
  "kickboxing",
]);

Person.prototype.farewell = function () {
  alert(this.name.first + " has left the building. Bye for now!");
};

 

해당 코드와 같이 Prototype 을 사용하여 수정이 가능하다. person 이라는 메소드를 정의하고, person1 이라는 객체를 생성하게 된다. 이 때 person1이 Prototype 객체가 된다. 그 후 Prototype 속성에 farewell이라는 새로운 메소드를 추가시켰다.

Prototype에 새로운 메소드를 추가하면 동일한 생성자로 생성된 모든 객체에서 추가된 메소드를 바로 사용할 수 있게 된다.

 

즉, person1 객체에서 farewell 메서드를 사용할 수 있게 된다. 이는 Prototype 객체는 모든 인스턴스에서 공유하기 때문에 정의하는 즉시 별도의 갱신 과정 없이 접근이 가능하다라는 것을 의미한다.

 

비어있는 객체를 만든 후 해당 객체에 대해 정의된 속성이나 메서드가 없더라도 object.prototype 의 내장된 속성 및 메서드가 존재한다.

String.prototype.upper = function(){}
let searchstring = "TEST"
searchstring.upper()

위와 같은 코드를 보게되면, String 객체의 prototype에 upper 메서드를 추가하므로, String 객체의 속성 및 메서드를 참조할 수 있다.

 


Prototype Chain

객체의 프로토타입은 또 다른 새로운 객체이며, 자체 프로토타입또한 가지고 있어야 한다. 즉, 프토토타입 체인이라 불리는 상위 객체로부터 속성 및 메소드를 상속 받으며, 또 다른 새로운 객체를 만들게 되면 새롭게 생성된 객체 또한 상위 객체로부터 속성 및 메소드를 상속받게된다. 이를 통해 특정 객체가 특정 속성에 접근하려 할 때 해당 객체를 탐색 후 찾지 못한다면 객체의 프로토타입 속성을 탐색하며, null을 가진 객체에 도달할 때 까지 지속된다.

 

proto 라는 속성을 통해 prototype에 액세스 할 수 있다.

username.__proto__                        // String.prototype
username.__proto__.__proto__              // Object.prototype
username.__proto__.__proto__.__proto__    // null

 

이와 같이 prototype을 통해 상속된 객체를 탐색하다 최상위 객체인 Object.prototype에 영향을 주게되면, 모든 객체에 속성이 포함되게 된다.


Prototype Pollution

Prototype Pollution은 JavaScript 환경에서 발생하는 심각한 보안 취약점이다.

공격자가 객체의 Prototype에 임의의 속성을 주입하여 애플리케이션의 기본 동작을 바꾸게 된다.

JavaScript 함수가 Key를 먼저 삭제하지 않고 사용자가 제어할 수 있는 속성을 포함하는 객체를 기존 객체에 재귀적으로 병합할 때 발생하며, 공격자는 __proto__와 같은 속성을 통해 중첩된 Prototype와 함께 proto와 같은 키를 가진 property를 삽입할 수 있다.

Property란 객체와 연관된 값을 의미함, 키와 값으로 구성된다. 이러한 오염은 모든 prototype를 오염시킬 수 있지만 내장된 전역 Object.prototype에서 가장 일반적으로 발생된다.

 

JavaScript 객체는 공통 조상인 Object.prototype을 통해 기본 속성과 메서드를 상속받는데, prototype이 오염(pollute)되면 모든 객체에 해당 속성이 전파되어 전체 어플리케이션 로직에 영향을 미치게 된다.

 

Prototype Pollution은 클라이언트 사이드(브라우저 환경)와 서버 사이드(Node.js 등의 백엔드 JavaScript 환경) 모두에서 발생할 수 있으며, 각각 영향 범위와 공격 기법에 차이가 있다. 라이언트 측에서는 주로 DOM XSS나 클라이언트 로직 변조로 이어지고, 서버 측에서는 응용 프로그램 흐름 제어 변경이나 심하면 RCE 등의 심각한 결과로 이어질 수 있다.

주로 발생하는 환경 및 취약 지점

사용자의 입력을 객체로 파싱하거나 병합하는 코드에서 주로 발생되며, 특정 프레임워크나 언어에 국한되지는 않지만, 

Node.js/Express 같은 JavaScript 백엔드나 프론트엔드 웹 애플리케이션에서 많이 보고되고 있다.

 

  • 객체 병합(Merge): 서버나 클라이언트에서 여러 객체를 합칠 때 사용자 제공 객체를 깊은 병합(deep merge)하거나 확장하면서 키 검증을 하지 않으면 발생할 수 있다. 예를 들어, Object.assign이나 $.extend 등의 함수를 부주의하게 사용할 경우 __proto__ 키를 가진 입력이 프로토타입을 오염시킬 수 있다. 특히 JSON 파싱 후 객체 병합이 취약한 패턴으로, JSON으로 받은 입력을 객체로 만든 뒤 기존 객체와 병합할 때 안전 검증이 없으면 공격자가 프로토타입 체인을 조작할 수 있다

 

  • 객체 클로닝(Cloning): 빈 객체에 사용자 입력 객체를 복사(clone)하는 구현도 사실상 병합과 동일하며, 이 과정에서 __proto__ 속성을 복사하면 똑같이 취약하다.

 

  • 직접 프로퍼티 설정: 애플리케이션이 사용자 입력값을 받아 객체의 특정 필드에 대입하는 기능이 있을 때, 사용자가 프로퍼티 이름 자체를 제어할 수 있다면 object[사용자입력_key] = value 대입 시 key를 __proto__로 설정하여 글로벌 프로토타입에 주입할 수 있다. 예를 들어 쿼리 파라미터나 JSON 필드로 __proto__를 전달하여 해당 필드에 값을 할당하면 프로토타입 오염이 일어난다.

이러한 취약점은 주로 입력 병합/파싱 관련 라이브러리들의 결함으로 나타났으며, jQuery, lodash, Express, minimist, hoek 등 다수의 인기 JavaScript 라이브러리에서 Prototype Pollution 취약점(CVE)들이 발견되었고 패치되었다.

Client Side Prototype Pollution

클라이언트 사이드에서는 브라우저 내 JavaScript 코드가 취약한 경우로, 공격자는 웹 페이지에서 전역 Object.prototype을 오염시켜 DOM 기반 XSS나 클라이언트 로직 변조를 일으킬 수 있다.

 

예를 들어, URL의 쿼리스트링이나 해시(fragment)를 파싱하여 객체로 만들고 그 데이터를 활용하는 클라이언트 코드가 있다면, 공격자가 URL에 __proto__ 파라미터를 삽입하여 프로토타입을 오염시킬 수 있더,

1. 취약점 식별

1-1. URL 쿼리 파라미터, 프래그먼트(#), 폼 입력, 혹은 애플리케이션이 처리하는 JSON 데이터 등이 대상이 된다.,

https://vulnerable-site.com/?__proto__[foo]=bar
https://vulnerable-site.com/?__proto__.foo=bar

 

1-2. 페이로드 주입 후, 브라우저 개발자 콘솔에서 전역 객체의 프로토타입에 해당 속성이 생겼는지 확인한다. 를 들어 위 URL을 방문한 후 콘솔에 Object.prototype.foo를 입력했을 때 "bar"와 같은 값이 나오면 프로토타입이 오염된 것이다. 반대로 undefined이면 아직 취약점이 트리거되지 않은 것이다.

1-3. __proto__ 문자가 필터링 되거나, 허용되지 않을 경우 constructor.prototype 경로로 우회하는 방안이 존재한다. 호긍ㄴ 유니코드 인코딩도 활용해볼 수 있다.

2. 취약점 검증

위에서 설명했듯, 식별 단계에서 속성을 주입했다면, 재확인 및 검증 단계를 거쳐야한다.

개발자 콘솔에서 Object.prototyupe.foo에 대한 조회를 통해 입력한 값이 보이는지를 확인하며, __proto__[foo]=bar 방식이 안되었지만 __proto__.foo=bar로는 되는 경우가 있을 수 있으므로, 서로 다른 표기법으로 프로토타입 속성 추가가 가능한지 확인 해야한다.

 

이후 클라이언트 측에서는 즉각적인 변화가 없으며, 해당 취약점이 실제 악용 가능한지에 대한 판단이 이루어져야한다.

이를 위해서는 가젯(gadget)를 찾아내야한다.

추가 악용 및 영향

클라이언트단에서 Prototype Pollution 취약점이 존재한다는 것을 확인 후 실제 영향을 주기 위해서는 오염된 프로토타입 속성을 활용하는 가젯을 찾아야 한다. 쉽게 말해 가젯은 어플리케이션 코드 내에서 프로토타입에 추가된 속성을 참조하거나 사용하는 지점을 의미한다. 

 

클라이언트단에서 prototype pollution 을 악용하는 대표적인 시나리오는 DOM XSS이다.

어플리케이션이 프로토타입에 추가된 값을 innerHTML, eval등 DOM 에 악용되는 요소들에 사용한다면,

오염된 값에 악성 스크립트를 심어 XSS 공격을 할 수 있다. 이 외에도, 인증/인가 우회또한 가능하다.

user.isAdmin과 같은 속성을 체크하여 관리자 기능을 수행한다고 할 때 Object.prototype.isAdmin=true 로 프로토타입을 오염시킴으로써 모든 사용자 객체가 관리자 권한을 가진것처럼 속일 수 있다.

 

Server Side Prototype Pollution

Prototype Pollution이 서버측에서 발견되면 이 영향력은 매우 심각하다. 

단순히 응답이 달라지는 것을 넘어, 응용 프로그램의 핵심 로직을 완전히 장악할 수 있다.

서버측에서도 동일하게, 취약점이 식별되면 가젯을 찾아 공격을 진행해야 한다.

취약점 식별

서버측에서는 클라이언트 측과 다르게, 테스트 과정에서 서버 내부 상태를 직접 볼 수 없으므로, 우회적으로 오염 여부를 판단하는 기법들이 존재한다.

  • 의심 앤드포인트 선정 : JSON 기반의 API(POST/PUT 요청 Body) 및 쿼리 파라미터를 객체로 사용하는 기능이 대표적인 앤드포인트가 된다.

 

  • 프로토타입 오염 시도 : 해당 요청에 __proto__ 필드를 삽입하여 Payload를 보낼 수 있다. 예를 들어, JSON Body가 {"name":"Alice", "age":20}라면 이를 {"name":"Alice", "age":20, "__proto__": { "pollutedKey": "123" }}처럼 만들어 보내보는 식으로 시도해볼 수 있다. 쿼리 파라미터의 경우 ?user=alice&__proto__[pollutedKey]=123 같은 형태로 보낼 수 있다.

 

  • 응답 변화 관찰 : 응답 코드나 메시지의 미묘한 변화를 찾아볼 수 있다.
    1. 상태 코드 변조 : Node.js의 일베 에러 처리 모듈은 에러 객체에 status 속성이 있다. 이를 이용해 서버에서 에러 발생 후 기본 응답코드와 __proto__ 를 통해 특이한 값의 status 속성을 주입하고 에러를 유발시킨다 이후 __proto__ 삽입 직후 HTTP 상태코드 변화가 발생됐다면, 이는 프로토타입 취약점이 존재할 가능성이 크다.
    2. JSON 응답 서식 변조 : Express 프레임워크에서는 서버가 JSON 응답을 주는 앤드포인트가 있다면, 우선 현재 응답의 들여쓰기 공백 수를 확인하고 이후 __proto__를 통해 json spaces: 10 같은 값을 주입한 뒤 다시 요청을 보냈을 경우 응답 JSON의 들여쓰기가 깊어지거나 줄바꿈이 달라졌다면, 프로토타입 취약점이 존재한다고 볼 수 있다.
    3. 기타 헤더/속성 변조 : 청의 Content-Type 헤더를 파싱하여 req._parsedOriginalUrl 등의 내부 객체에 charset 정보를 담는데, 이때 __proto__로 content-type 헤더 값을 조작하면 응답에서 문자열 인코딩 처리가 바뀌는 현상을 유도할 수 있다. charset=utf-7을 주입하고 UTF-7 인코딩된 문자열을 보내보는 기법을 통해, 응답에서 해당 문자열이 정상 디코딩되어 나타나면 오염 성공을 유추할 수 있다.

 

  • 유효성 검사 우회 : 어플리케이션의 입력 유효성 검사 동작 변화를 이용할 수 있다. BlackHills 보안 연구에서는 이를 이용한 방안이 제시되었다. 어떤 API가 요구하는 필드가 없으면 400 Bad Request를 반환한다고 할 때, 그 필드를 아예 포함하지 않고 대신 모든 내용을 __proto__ 객체 안에 넣어 보낸 후 정상적이라면 필드 누락으로 400이 날 텐데, 실제로는 200 OK가 나오면 프로토타입 키가 특별 처리되어 검사를 우회했을 가능성이 크다고 볼 수 있따. 이후 __proto__ 대신 무의미한 키(예: false_positive)로 동일한 요청을 보내 400 응답이 나오는지 비교함으로써, 오직 __proto__일 때만 통과한다면 취약점이 존재한다고 확신할 수 있다.

 

취약점 검증

식별 단계에서 변화를 관찰 했다면, 서버측 프로토타입 오염에서는 json spaces 기법으로 포맷 변화를 확인할 수 있었을 것이다. 한번 더 요청을 보내 속성 오염을 기본 값으로 되돌릴 수 있는지 확인하여, 안전하게 취약점을 재현할 수 있다. 이와 같이 status등 다양한 속성들을 오염시켰다면, 다시 정상 값으로 돌려놓을 수 있어야 한다.

 

추가 악용 및 영향

서버측의 프로토타입 오염은 클라이언트측과 다르게, 크 피해는 확연한 차이가 존재한다.

  1. 권한 상승 : 사용자의 권한을 지정하고, 검증하는 플래그 혹은 역할 정보가 객체 내에 존재하는 경우, 이를 조작/우회 하게 된다면 의도한 역할 이상의 행동을 할 수 있게 된다.
  2. 비즈니스 로직 변조 : 모든 객체에 공통적으로 영향을 줄 수 있는 취약점인 만큼, 중요한 속성 값을 조작할 경우, 개발자가 의도한 로직을 벗어나 공격자가 원하는 로직을 실행시킬 수 있게 된다.
  3. 원격 코드 실행(RCE) : 프로토타입의 toString 같은 핵심 함수들을 문자열로 덮어씌워 서버 코드 어디에서든 함수 호출 시 의도하지 않은 동작을 일으킬 수 있다. 더 나아가 이를 악용하여 Dos와 같은 공격이 이루어 질 수도 있다.

객체 병합/셋(set) 기능을 사용하며, 자주 발생되는 앤드포인트

1) 설정/프로필/환경설정 저장 엔드포인트 (PUT/PATCH/POST)

  • 패턴: /api/user/settings, /api/admin/site/options, /api/projects/:id/config
  • 메커니즘: 사용자 JSON을 deep merge(예: _.merge, deep-extend, jQuery.extend(true, ...), Object.assign+재귀)로 서버 측 기본 설정에 병합.
  • PoC 키:
    • JSON: {"__proto__":{"polluted":"yes"}}, {"constructor":{"prototype":{"polluted":"yes"}}}
    • x-www-form-urlencoded/Query: __proto__[polluted]=yes
  • 영향: 전역(모든 객체)에 속성이 주입되어 권한 체크 우회, 템플릿 렌더링/XSS 유발, 로깅/플래그 로직 변조 등.

2) “부분 업데이트(PATCH)”나 “임의 속성 허용” 모델

  • 패턴: /api/*/update, /api/*/patch (스키마 검증 없이 임의 키 허용)
  • 메커니즘: DTO/스키마 없이 받은 바디를 기존 객체에 깊은 대입. 키 필터링 부재.
  • PoC 키: prototype, __proto__, constructor.prototype
  • 영향: 인증/인가 속성(isAdmin, role) 조작, 미들웨어 동작 조건 오염.

3) 검색/정렬/필터 객체를 받는 엔드포인트

  • 패턴: /api/items?filter[...]=..., /search 바디에 { sort:{}, filter:{} }
  • 메커니즘: qs/body-parser가 쿼리/폼을 객체로 변환(toObject) → 그 객체를 그대로 merge/set에 사용.
  • PoC 키(쿼리): ?__proto__[polluted]=1, ?constructor[prototype][polluted]=1
  • 영향: 서버 전역 동작 플래그 오염, SSR 템플릿/뷰 헬퍼 오염.

4) 가져오기/임포트 기능(환경설정·테마·번역사전·대시보드 레이아웃)

  • 패턴: /api/import, /admin/theme/upload, /i18n/upload
  • 메커니즘: 업로드된 JSON/YAML을 신뢰하고 기본 설정과 병합.
  • PoC 키: 위와 동일(프로토타입 키), YAML일 경우 __proto__: 섹션으로 주입.
  • 영향: 관리자 UI/렌더링 파이프라인에 전역 속성 주입.

5) Webhook/3rd-party JSON 수신 엔드포인트

  • 패턴: /webhook/* (Git/결제/메시징 시스템)
  • 메커니즘: 외부가 보내는 페이로드를 그대로 내부 이벤트 객체에 병합하거나, 큐 작업 설정에 반영.
  • PoC 키: constructor.prototype
  • 영향: 비동기 작업기/로그 처리/알림 라우팅 로직 오염.

6) 플러그인/확장(Extension) 옵션 저장

  • 패턴: /plugins/:id/settings
  • 메커니즘: 각 플러그인 마다 옵션 스키마가 약함 → 공통 merge 유틸 재사용.
  • PoC 키: __proto__, prototype
  • 영향: 다른 플러그인/핵심 모듈까지 전파되는 전역 오염.

7) GraphQL에서 “generic JSON” 스칼라 입력

  • 패턴: mutation updateSettings(input: JSON)
  • 메커니즘: 스키마는 느슨, 리졸버에서 deep merge.
  • PoC 키: 동일
  • 영향: 서버 런타임 전역 오염, 캐시/권한 로직 변형.

라이브러리/프레임워크 측면에서 자주 보이는 원인

  • 서버 사이드(Node.js): lodash.merge, lodash.defaultsDeep, deep-extend, merge.recursive, hoek, 오래된 qs 옵션(기본값으로 프로토타입 키 허용), 커스텀 deepSet/deepAssign 구현.
  • 클라이언트 사이드: $.extend(true, ...), 프론트 설정객체를 사용자 입력과 병합 → XSS/로직 우회로 연결.
  • 공통: set(obj, 'a.b.c', val) 계열 유틸이 점/브래킷 경로를 방문하며 프로토타입 키를 차단하지 않음.

Prototype Pollution Bypass

1) 경로(key) 변형으로 __proto__ 필터 우회

필터가 __proto__ 문자열 자체만 차단 한다면, 같은 의미를 만드는 표기 변형으로 우회할 수 있다.

  • Dot vs Bracket 변형
    • ?__proto__.polluted=1
    • ?__proto__[polluted]=1
    • ?__proto__%5Bpolluted%5D=1 (URL-인코딩)
  • 이중/부분 인코딩
    • ?%255F%255Fproto%255F%255F[polluted]=1 (서버가 두 번 디코드하면 우회)
    • ?__pr%6Fto__[polluted]=1 (o만 %인코딩)
  • JSON Body 경로
    • {"__proto__":{"polluted":1}} (JSON 파서 → 딥머지에서 오염)

2) Constructor.protorype 체인

{}의 constructor는 Object를 가리킨다. 따라서 {"constructor":{"prototype":{"polluted":1}}}
가 병합되면 Object.prototype.polluted = 1 과 같은 효과가 발생한다.

  • 폼/쿼리 예시
    • ?constructor[prototype][polluted]=1
  • JSON 예시
    • {"constructor":{"prototype":{"polluted":1}}}

3) 대체 프로토타입 표적 : Array.prototype / 길이 기반 DoS

모든 객체가 아닌 배열 프로토타입을 오염해도 영향을 줄 수 있다.

예를 들어, length 를 비정상적으로 키우거나, 배열 메서드를 바꾸면 순회/병합 과정에서 증폭 혹은 예외로 이어지며 DoS가 발생된다.

  • ?constructor[prototype][length]=999999999 (Array를 겨냥한 가젯과 결합 시)
  • ?__proto__[push]=function(){/*...*/} (클라/서버 코드가 배열 메서드를 신뢰하는 경우)

4) Getter/Setter 기반 사이드이펙트 (__defineGetter__ 등) [위험]

원리: Object.prototype.__defineGetter__/__defineSetter__ 를 이용해 읽기만 해도 실행되는 속성을 심을 수 있음.

예시

  • ?__proto__[danger]=1&__proto__[__defineGetter__][danger]=alert (환경 의존)
  • 이후 obj.danger 접근 시 alert 트리거(브라우저·코드에 따라 다름).
  • Object.prototype.__defineGetter__('boom', fn) / Object.defineProperty(..., {get(){…}}) 등으로 접근자(함수)를 주입

5) hasOwnProperty/toString 등 핵심 메서드 덮어쓰기로 방어 우회

[위험]

if (obj.hasOwnProperty(k)) 같은 체크에 의존한다면,
Object.prototype.hasOwnProperty = ()=>true 처럼 검사 자체를 무력화할 수 있음. 

# PortSwigger/OWASP는 “키 정화 없이 병합하지 말라”는 이유로 이 류의 리스크를 짚음

6 CSP·가젯 결합 우회 (클라이언트 사이드)

Prototype Pollution은 속성 주입에 불과하므로, 실행시점인 가젯(sink)를 노려야한다.

transport_url 같은 URL성 키를 오염시킨 뒤 스크립트 로더/JSONP가 이를 사용하면 data:/blob: 등으로 DOM XSS가 발생할 수 있다.

예시

  • ?constructor[prototype][transport_url]=data:text/javascript,alert(1)
  • 앱의 로더가 cfg.transport_url을 <script src=...>에 쓰면 실행.

참고 자료

1. https://portswigger.net/web-security/prototype-pollution/server-side#:~:text=JavaScript%20was%20originally%20a%20client,side%20contexts
2. https://learn.snyk.io/lesson/prototype-pollution/?ecosystem=javascript
3. https://www.yeswehack.com/learn-bug-bounty/server-side-prototype-pollution-how-to-detect-and-exploit
4. https://blog.doyensec.com/2024/02/17/server-side-prototype-pollution-Gadgets-scanner.html#:~:text=Prototype%20pollution%20has%20recently%20emerged,code%20execution%2C%20under%20certain%20conditions
5. https://www.blackhillsinfosec.com/hit-the-ground-running-with-prototype-pollution/#:~:text=We%20can%20use%20this%20input,returns%20a%20200%20OK%20response
6. https://www.netspi.com/blog/technical-blog/web-application-pentesting/ultimate-guide-to-prototype-pollution/#:~:text=The%20three%20most%20common%20JavaScript,to%20the%20prototype%20being%20polluted