설명
사용자 입력으로 중첩 객체의 속성 경로를 정할 때 __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__등을 통해 공유 프로토타입에 값을 쓸 수 있습니다. - 변경 후: 경로 요소의 타입과 위험 키를 먼저 검사하고, 중간 단계에서는 객체 자신의 속성만 따라갑니다. 새 저장소는 프로토타입 없이 만듭니다.