프로토타입 오염 (Prototype Pollution)

Prototype Pollution

설명

사용자 입력으로 중첩 객체의 속성 경로를 정할 때 __proto__ 또는 constructor.prototype을 따라가면 프로토타입 자체에 값을 쓸 수 있습니다. 공유 프로토타입이 오염되면 이를 상속하는 다른 객체에도 공격자가 넣은 속성이 나타날 수 있습니다.

잠재적 영향

  • 오염된 속성을 읽는 애플리케이션 로직이 예상과 다르게 동작할 수 있습니다.
  • 인증·인가 판단에 해당 속성을 사용하면 보안 검증이 우회될 수 있습니다.
  • 함수나 설정값이 바뀌면 장애나 데이터 손상으로 이어질 수 있습니다.

해결 방법

  • 모든 경로 요소가 문자열인지 확인하고 __proto__, constructor, prototype은 차단하세요.
  • 변경 가능한 필드를 서버의 허용 목록으로 제한하고, 중첩 탐색에서는 상속 속성을 따라가지 마세요.
  • 사전처럼 쓰는 저장소는 Object.create(null)이나 Map을 고려하세요.
  • Lodash, dot-prop 같은 라이브러리를 사용한다면 보안 패치를 적용하세요. 라이브러리 사용만으로 입력 검증이 대체되지는 않습니다.

예시

변경 전

javascript
// 취약한 동적 key 설정 예시
function setDeepValue(obj, keys, value) {
  keys = Array.isArray(keys) ? keys : keys.split('.')
  let cur = obj
  while (keys.length > 1) {
    const key = keys.shift()
    // 취약: key가 '__proto__' 등일 때도 그대로 진행
    if (!cur[key]) cur[key] = {}
    cur = cur[key]
  }
  cur[keys[0]] = value
}
// setDeepValue(user, ['__proto__', 'isAdmin'], true);

변경 후

javascript
// 안전한 동적 key 설정 예시
function setDeepValueSafe(obj, keys, value) {
  const blockedKeys = ['__proto__', 'constructor', 'prototype']
  keys = Array.isArray(keys) ? [...keys] : keys.split('.')
  if (keys.length === 0 || keys.some(key =>
      typeof key !== 'string' || blockedKeys.includes(key))) {
    throw new Error('위험한 속성명입니다.')
  }
  let cur = obj
  while (keys.length > 1) {
    const key = keys.shift()
    if (!Object.hasOwn(cur, key)) cur[key] = Object.create(null)
    cur = cur[key]
    if (cur === null || typeof cur !== 'object') {
      throw new Error('위험한 속성명입니다.')
    }
  }
  cur[keys[0]] = value
}

설명:

  • 변경 전: 제한 없이 속성 경로를 따라가므로 __proto__ 등을 통해 공유 프로토타입에 값을 쓸 수 있습니다.
  • 변경 후: 경로 요소의 타입과 위험 키를 먼저 검사하고, 중간 단계에서는 객체 자신의 속성만 따라갑니다. 새 저장소는 프로토타입 없이 만듭니다.

참조