FlawOpen

flawopen.com/프로토타입 오염/JSON.parse()

JSON.parse()가 프로토타입 오염을 일으키나요?

CWE-1321: 프로토타입 오염참고 가이드
핵심 답변

아닙니다. JSON.parse()는 __proto__라는 자체 데이터 속성을 만들며, 파싱 중 상속된 setter를 호출하거나 프로토타입을 바꾸지 않습니다. 이후 코드가 setter를 통해 해당 키를 복사하거나, 신뢰할 수 없는 키를 재귀 병합하거나, 공격자가 제어하는 객체 경로에 기록할 때 위험이 생깁니다.

안전하지 않은 복사 — 로컬 대상만
// JSON.parse는 데이터를 만들 뿐 프로토타입을 오염시키지 않는다.
const input = '{"__proto__":{"isAdmin":true}}';
const parsed = JSON.parse(input);

// Object.assign은 대상에 Set을 사용한다. 여기서는
// 이 로컬 데모 객체의 프로토타입만 바뀐다.
const copied = Object.assign({}, parsed);

Object.getPrototypeOf(copied).isAdmin; // true
Object.prototype.isAdmin;              // undefined
안전한 처리 — 알려진 구조만 구성
// 파싱 후 애플리케이션이 허용한 필드만 구성한다.
const input = '{"theme":"dark","locale":"en","__proto__":{"isAdmin":true}}';
const parsed = JSON.parse(input);
const config = {
  theme:  parsed.theme,
  locale: parsed.locale,
};

// 신뢰할 수 없는 키의 맵에서는 상속 setter를 피한다.
const options = Object.assign(Object.create(null), parsed);

JSON.parse()가 오염 지점이 아닌 이유

JSON 키와 객체 리터럴의 프로토타입 setter는 서로 다른 동작이다. JSON 파싱은 새 값에 자체 속성을 정의하며 obj.__proto__ = value를 실행하지 않는다. 후속 작업은 다르게 동작할 수 있다. Object.assign(target, source)는 대상에 대입하므로 자체 __proto__ 키가 대상의 상속 setter를 호출할 수 있다. 이는 대상 객체의 프로토타입을 바꾸지만, 그 자체로 Object.prototype을 수정하지는 않는다.

const o = JSON.parse('{"__proto__":{"a":1}}') 실행 후 Object.hasOwn(o, "__proto__")는 true이고 Object.prototype.a는 계속 undefined다. 이후 일반 대상에 이 키를 복사하면 대상의 프로토타입이 달라질 수 있다. 객체 spread는 setter를 호출하지 않고 자체 속성을 만들지만, 안전하지 않은 재귀 병합이나 경로 쓰기를 막지는 않는다.

실제 위험이 발생하는 곳

파싱한 JSON은 신뢰할 수 없는 데이터이지 오염된 프로토타입이 아니다. 위험은 이후 쓰기 지점에 달려 있다. 재귀 병합, 경로 setter, 일반 객체에 대입하는 복사 루프가 해당한다. 취약한 재귀 병합은 __proto__ 또는 constructor.prototype을 따라 공유 프로토타입에 도달할 수 있고, 한 단계 복사는 대상만 바꿀 수도 있다. 영향을 설명하기 전에 실제 함수와 대상을 추적하라.

신뢰할 수 없는 키가 쓰이는 위치를 검토하라. JSON 파싱뿐 아니라 deep-merge 함수, 객체 경로 setter, 대입을 수행하는 for...in 루프가 대상이다. 사용자가 키를 제어할 수 있다면 __proto__, constructor, prototype을 거부하고, 검증된 구조로 다시 구성하거나 프로토타입 없는 사전을 사용하라.

자주 묻는 질문

항상 reviver로 __proto__를 제거해야 하나요?

앱에서 필요하지 않다면 reviver가 파싱 중 해당 키를 제거할 수 있다. 이는 심층 방어이며 안전하지 않은 병합이나 경로 쓰기를 고치는 대신은 아니다. constructor.prototype과 JSON 이외의 입력도 확인해야 한다.

Express 또는 body-parser가 보호해 주나요?

본문 파서는 JavaScript 값을 만들 뿐 이후 복사나 병합을 안전하게 만들지 않는다. 실제 파서와 후속 작업을 검토하라. 일부 쿼리 문자열 파서는 대괄호 표기에서 중첩 객체를 만들기도 하는데 별도의 입력 경로다.

JSON.stringify()가 프로토타입 오염 위험인가요?

아니다. 직렬화는 값을 읽어 JSON 텍스트를 만들며 프로토타입을 바꾸지 않는다. 해당 텍스트를 다시 파싱해 안전하지 않은 sink로 보내면 위험이 다시 생길 수 있다.

참고 자료

함께 보기 JavaScript 프로토타입 오염: 원인과 대응 JSON.parse()는 eval()보다 안전한가요? lodash.merge()는 프로토타입 오염에 안전한가요? Object.assign()과 Prototype Pollution: 무엇이 안전한가요?