프로토타입 오염 (Prototype Pollution)

Prototype Pollution

설명

외부 입력으로 객체를 선택하고 그 속성에 값을 쓰면, __proto__ 같은 키를 통해 공유 프로토타입에 접근할 수 있습니다. 오염된 프로토타입을 상속하는 객체에서 의도하지 않은 속성이 읽히며, 이를 사용하는 애플리케이션 로직에 따라 보안 문제가 발생할 수 있습니다.

잠재적 영향

  • 오염된 속성을 사용하는 기능이 예상과 다르게 동작할 수 있습니다.
  • 데이터 처리나 설정이 바뀌어 데이터가 손상될 수 있습니다.
  • 인증·인가 판단이 오염된 값에 의존하면 검증이 우회될 수 있습니다.

해결 방법

  • 최종 필드명뿐 아니라 중간 객체를 선택하는 키도 검증하고, __proto__, constructor, prototype을 차단하세요.
  • 변경 가능한 필드는 서버가 소유한 허용 목록으로 제한하세요. 요청으로 전달받은 허용 목록은 신뢰하지 마세요.
  • Object.hasOwn으로 상속 속성을 구분하고, 사전 형태의 저장소에는 Object.create(null)이나 Map을 고려하세요.

예시

변경 전

javascript
app.get('/test/:id', (req, res) => {
    let id = req.params.id;
    let items = req.session.todos[id];
    if (!items) {
        items = req.session.todos[id] = {};
    }
    // 위험: 외부 입력으로 바로 키를 사용
    items[req.query.name] = req.query.text;
    res.sendStatus(200);
});

변경 후

javascript
app.get('/test/:id', (req, res) => {
    const id = req.params.id;
    const blockedKeys = new Set(['__proto__', 'constructor', 'prototype']);
    const allowedKeys = new Set(['title', 'text', 'done']);
    if (blockedKeys.has(id) || !allowedKeys.has(req.query.name)) {
        return res.status(400).send('Invalid key name');
    }
    if (!Object.hasOwn(req.session.todos, id)) {
        req.session.todos[id] = Object.create(null);
    }
    const items = req.session.todos[id];
    items[req.query.name] = req.query.text;
    res.sendStatus(200);
});

설명:

  • 변경 전: id가 __proto__이면 공유 프로토타입을 선택할 수 있고, 검증하지 않은 필드명으로 값을 씁니다.
  • 변경 후: 객체 선택 키와 최종 필드를 모두 검사하고, 상속 속성을 기존 항목으로 취급하지 않습니다. 세션의 todos 저장소가 초기화되어 있다고 가정합니다.

참조