외부에서 데이터를 받았더니, 그 뒤로 설정한 적 없는 속성이 관계없는 객체에 나타납니다. 또는 hasOwnProperty 같은 표준 메서드가 갑자기 "함수가 아니다"라며 앱이 쓰러집니다. 둘 다 프로토타입 오염의 증상입니다.
Node.js가 책임지는 범위와 남겨진 범위
Node.js는 이것이 자기 취약점이 아니라고 분명히 말한다
Node.js 공식 보안 가이드는 명확합니다. "Node.js 위협 모델에 따라, 공격자가 사용자 입력을 제어하는 데 의존하는 프로토타입 오염은 Node.js 코어의 취약점으로 보지 않는다. Node.js는 애플리케이션 코드가 넘기는 입력을 신뢰하기 때문이다."
그리고 바로 이어서 이렇게 말합니다. "그럼에도 프로토타입 오염은 Node.js 애플리케이션과 서드파티 라이브러리에 심각한 취약점 유형이며, 애플리케이션과 의존성 수준에서 방어를 구현해야 한다."
모순이 아니라 책임의 소재를 밝힌 것입니다. 런타임은 입력이 올바르다는 것을 보증하지 않는 쪽에 서 있습니다. 즉 "CVE를 기다린다, 업데이트를 기다린다"는 여기서 통하지 않습니다. 본 사이트가 거듭 마주치는 일의 또 다른 예입니다. 누군가 처리하고 있을 거라고 생각한 부분에, 실은 책임지는 사람이 없습니다.
무엇이 잘못되나 (두 가지 결과)
외부 입력(JSON, 쿼리, 폼)
특수한 키를 담고 있다
↓ 재귀 병합 / 깊은 복사 / 설정 조립
공유 원형(프로토타입)이 바뀐다
↓ 그것을 상속하는 모든 것도 바뀐다
1. 설정한 적 없는 값이 나타난다
권한 플래그와 옵션 판정이 틀어진다
2. 내장 메서드가 사라진다
관계없는 곳에서 앱이 죽는다(DoS)
- 1. 속성 주입
- "설정될 리 없는" 값을 읽을 수 있게 됩니다. 옵션 객체나 권한 플래그를 단순한 존재 여부로 판정하는 곳에서는 주입된 값을 그대로 믿습니다. 권한 판정에 닿으면 권한 문제가 되고, 템플릿이나 명령 조립에 닿으면 훨씬 심각해질 수 있습니다
- 2. 서비스 거부
- 원형 자체가 망가지면
hasOwnProperty같은 내장 메서드가 "is not a function" 오류를 냅니다. 문서는 이것을 잠재적 DoS로 제시합니다. 기억할 점은 이것이 권한 상승만의 문제가 아니라는 것입니다 - 미치는 범위
- 문서가 드는 예는 Node.js 자체(CVE-2022-21824)와 서드파티 라이브러리(Lodash, CVE-2018-3721)입니다. 재귀 병합을 직접 쓰지 않았어도, 의존성이 썼다면 달라지는 것이 없습니다
문서가 드는 대책 (7가지)
Node.js가 직접 열거하고 있습니다. 하는 일별로 묶으면 적용하기 쉽습니다. 입구에서 막기, 구조를 오염되지 않게 하기, 읽는 방식 바꾸기입니다.
입구에서 막는다
・안전하지 않은 재귀 병합을 피한다(문서는 CVE-2018-16487을 예로 든다)
・외부·신뢰할 수 없는 요청에 JSON Schema 검증을 구현한다
예상하지 못한 키를 아예 들이지 않는 것이 가장 확실합니다. 깊은 객체를 통째로 받아 기존 상태에 병합하는 설계를 다시 생각해 보세요.
구조와 읽는 방식을 바꾼다
・Object.create(null)로 프로토타입이 없는 객체를 만든다(사전 용도에 적합)
・Object.freeze(MyObject.prototype)로 프로토타입을 동결한다
・Node의 --disable-proto 플래그로 Object.prototype.proto를 비활성화한다
・Object.hasOwn(obj, key)로 속성이 그 객체 자신의 것인지 확인한다
・Object.prototype의 메서드를 쓰지 않는다
가장 효과가 큰 변경은 존재 여부를 확인하는 방식이다
실무에서 가장 넓게 효과가 있는 것은 Object.hasOwn(obj, key)로 바꾸는 것입니다. "이 속성이 있는가"라는 단순한 확인은 원형에서 상속된 값에도 "있다"고 답하고, 주입된 값이 채택되는 것이 바로 이 때문입니다. 속성이 그 객체 자신의 것인지 확인하는 것만으로 결과 1의 대부분을 막을 수 있습니다.
같은 이유로 객체에서 직접 obj.hasOwnProperty(key)를 호출하는 것도 피하세요. 그 메서드는 오염으로 지워질 수 있는 것 중 하나입니다(결과 2).
내 코드에서 확인할 것
외부 입력을 깊은 객체로 받고 있는가?
요청 본문, 쿼리 문자열, 폼, 웹훅 페이로드 등 중첩된 구조를 통째로 받아 기존 상태에 병합하는 곳을 모두 찾습니다. 외부 입력이 들어오는 곳은 거기뿐입니다. 스키마로 받을 키를 명시적으로 정하는 것이 가장 빠른 해결책입니다.
사전처럼 쓰는 객체를 만드는 방식을 바꾼다
"키가 실행 중에 정해지는 그릇"으로 쓰는 것(ID를 키로 하는 맵, 설정 묶음)은 **Object.create(null)**로 만듭니다. 원형이 없으면 상속받을 것도 없습니다. 검증으로 풀어야 할 것을 구조로 해결하는 방법입니다.
존재 여부 확인을 Object.hasOwn으로 통일한다
속성의 존재 여부에 따라 갈리는 분기를 검색해 바꿉니다. 권한, 플래그, 옵션 판정이 먼저입니다. 인가를 구현하는 방식과 직접 이어지기 때문입니다.
의존성은 기계에 맡긴다
재귀 병합을 직접 쓰지 않았어도 의존성이 썼다면 달라지는 것이 없습니다. 문서가 드는 예에 서드파티 라이브러리가 포함된 이유입니다. 의존성의 알려진 취약점은 사람 손으로 추적할 수 없으므로, osv-scanner 시작하기처럼 기계적 감시에 맡기세요. 오염된 의존성이 가져오는 더 넓은 위험은 npm 공급망 공격 방어에서 다룹니다.
서버 프레임워크의 입구도 같은 눈으로 본다
모든 Node 프레임워크에는 요청 본문을 역직렬화하는 경로가 있습니다. 본 사이트가 NVD 데이터를 확인했을 때, Next.js의 취약점은 캐시, 미들웨어, Server Actions 같은 서버 쪽 역할에 몰려 있었습니다(Next.js와 React는 취약점이 많은가?). **"프런트엔드 라이브러리니까 해당 없다"**는 전제부터 버리세요.
본 사이트의 견해: 책임 범위가 명시된 곳은 스스로 확인하라
이 글에서 운영상 가장 쓸모 있는 것은 대책 목록이 아니라 Node.js 코어의 취약점으로 보지 않는다는 문장입니다. 책임 범위가 이렇게 분명히 적힌 곳에서는, 그 범위 밖의 일을 아무도 처리하지 않습니다. CVE도 나오지 않고, 업데이트도 고쳐 주지 않습니다.
본 사이트는 이것을, 탈취를 막아 주지 않는 인증서(서브도메인 탈취)나 그 아래의 기존 정책을 지우지 않는 차단 설정(공개 버킷)과 같은 형태의 문제로 봅니다. 처리되고 있다고 생각한 곳의 실제 책임자는 누구인가? 이 질문에 한 번 답해 보면 우선순위가 꽤 바뀌는 경우가 많습니다.
출처(공식)
- Node.js, "Security Best Practices" (Prototype Pollution Attacks / CWE-1321 절) — nodejs.org (위협 모델상의 입장, 두 가지 결과, 인용된 CVE, 7가지 대책은 모두 이 문서에서 나왔습니다. 2026년 9월 5일 확인)
다음으로 읽기
- 같은 형태: 안전하지 않은 역직렬화 (외부 데이터가 타입을 정하는 문제)
- 의존성: osv-scanner 시작하기 / npm 공급망 공격 방어
- 전체 상황: Next.js와 React는 취약점이 많은가? (서버 쪽에서 나타나는 것)
- 설계: 인증과 인가의 차이 (주입된 값이 권한 판정에 닿지 않게)
FAQ
Q프로토타입 오염은 Node.js의 버그인가요? 업데이트로 고쳐지나요?
고쳐 줄 업데이트는 오지 않습니다. Node.js 공식 보안 가이드는 Node.js 위협 모델에 따라, 공격자가 사용자 입력을 제어하는 데 의존하는 프로토타입 오염은 Node.js 코어의 취약점으로 보지 않는다고 밝힙니다. Node.js는 애플리케이션 코드가 넘기는 입력을 신뢰하기 때문입니다. 이어서, 그럼에도 프로토타입 오염은 Node.js 애플리케이션과 서드파티 라이브러리에 심각한 취약점 유형이므로 애플리케이션과 의존성 수준에서 방어해야 한다고 말합니다. 설계상 개발자의 책임입니다.
Q실제로 무엇이 잘못되나요?
결과는 두 가지입니다. 첫째, 설정한 적 없는 속성이 프로세스 곳곳의 객체에 나타납니다. 권한 플래그나 옵션을 단순히 존재 여부로 판정하고 있다면 주입된 값을 그대로 믿게 되어 권한 판정이나 업무 로직이 틀어집니다. 둘째, 서비스 거부입니다. 내장 프로토타입이 망가지면 hasOwnProperty 같은 표준 메서드가 'is not a function' 오류를 내고, 입력과 관계없는 코드가 죽습니다. 두 번째 결과는 이것이 권한 상승만의 문제가 아니라는 것을 보여 줍니다.
Q어디를 고치면 되나요?
입구와 데이터 구조입니다. 입구에서는 외부·신뢰할 수 없는 요청에 JSON Schema 검증을 적용해 예상하지 못한 키가 아예 들어오지 못하게 합니다. 구조에서는 안전하지 않은 재귀 병합을 그만두고, 사전처럼 쓰는 객체는 Object.create(null)로 만들어 프로토타입 자체를 없앱니다. 읽을 때는 Object.hasOwn(obj, key)로 속성이 그 객체 자신의 것인지 확인하고, Object.prototype의 메서드에 기대지 않습니다. 운영 측면에서는 Object.freeze와 Node의 --disable-proto 플래그도 있습니다.
Q재귀 병합을 직접 쓴 적이 없는데, 안전한가요?
아닙니다. 문서가 드는 예는 Node.js 자체(CVE-2022-21824)와 서드파티 라이브러리(Lodash, CVE-2018-3721) 둘 다입니다. 설정 병합, 쿼리 문자열 파싱, 폼 역직렬화는 모두 널리 쓰이는 라이브러리가 대신 깊은 객체를 조립하는 곳입니다. 그래서 방어는 내 코드 안에서 끝나지 않고, 의존성을 기계적으로 감시하는 일과 짝을 이룹니다.