
CVE-2026-44789 — n8n <1.123.43 Prototype pollution nella paginazione delle richieste HTTP a RCE (gadget NODE_OPTIONS runner-spawn). Laboratorio + PoC automatizzato, verificato e2e.
Un creatore di flussi di lavoro autenticato n8n
< 1.123.43inquinaObject.prototypenel processo server di n8n attraverso le impostazioni di paginazione del nodo HTTP Request, poi scala a esecuzione remota di codice abusando di come n8n genera il suo task runner — perdendo unNODE_OPTIONSinquinato in un processonodefiglio e bypassando la sandbox del nodo Code.
| CVE | CVE-2026-44789 |
| Avviso | GHSA-c8xv-5998-g76h |
| Affetto | < 1.123.43, 2.0.0-rc.0 … < 2.20.7, 2.21.0 … < 2.22.1 |
| Corretto | 1.123.43 / 2.20.7 / 2.22.1 |
| Classe | CWE-1321 (Prototype Pollution) → CWE-94 (RCE) |
| CVSS | 9.4 (CVSS 4.0) / Critico |
| Auth | Autenticato (permesso di creare/modificare flussi di lavoro) |
| Stato | CONFERMATO — intera catena riprodotta end-to-end contro n8nio/n8n:1.123.42 |
L'avviso pubblico afferma solo che l'inquinamento "combinato con altre tecniche potrebbe portare a RCE" — non divulga un gadget. Questo repository documenta e automatizza una catena concreta e verificata di prototype-pollution→RCE.
packages/nodes-base/nodes/HttpRequest/V3/HttpRequestV3.node.ts, modalità di paginazione
updateAParameterInEachRequest:
paginationData.request[parameter.type]![parameterName] = parameterValue;
parameter.type, parameterName e parameterValue provengono tutti dal JSON del flusso
di lavoro (controllato dall'attaccante). Con parameter.type = "__proto__",
paginationData.request["__proto__"] risolve in Object.prototype, quindi l'assegnazione
scrive Object.prototype[parameterName] = parameterValue — un inquinamento del prototipo
globale nel processo server di n8n.
La correzione usa Object.create(null) per paginationData.request, quindi ["__proto__"]
è una chiave ordinaria (null-proto) invece del prototipo.
packages/cli/src/task-runners/task-runner-process-js.ts genera il task runner JS:
return spawn('node', [...flags, startScript], { env: this.getProcessEnvVars(...) });
Il normalizeSpawnArguments di Node costruisce l'ambiente del figlio con
for (const key in env), che enumera le proprietà enumerabili ereditate.
Inquinare Object.prototype.NODE_OPTIONS = "--require=/path/evil.js" quindi perde
nell'ambiente del runner generato. Il figlio è node, che rispetta NODE_OPTIONS,
quindi esegue --require del file dell'attaccante all'avvio → esecuzione di codice
all'esterno della sandbox del nodo Code, come utente del servizio n8n.
Il runner viene avviato all'avvio, ma il suo ciclo di vita lo rigenera ogni volta che
il processo esce (onProcessExit → start()). L'attaccante forza una rigenerazione dopo
l'inquinamento bloccando il runner con un nodo Code (task-timeout / OOM lo uccidono).
exploit.py)1. scrivi /tmp/evil.js via nodi Set → Convert to File → Read/Write Files
2. blocca il task runner via un nodo Code ( while(true){} ) [PRIMA dell'inquinamento]
3. inquina Object.prototype.NODE_OPTIONS via il nodo HTTP Request ( type="__proto__" )
4. il runner bloccato va in timeout → il processo principale rigenera 'node' → eredita NODE_OPTIONS
→ require('/tmp/evil.js') → RCE
L'ordinamento è importante: l'inquinamento rende NODE_OPTIONS una chiave enumerabile
non propria su Object.prototype, che il livello TypeORM di n8n incontra (for…in sulle
entità), rompendo la persistenza del flusso di lavoro. Quindi il runner deve essere già
bloccato prima che l'inquinamento atterri; la rigenerazione quindi raccoglie l'ambiente
inquinato.
Ambito di configurazione (leggere prima). La primitiva di inquinamento del prototipo si attiva su qualsiasi versione interessata senza modifiche di configurazione. Il gadget RCE necessita del task runner, che viene raggiunto diversamente per ogni ramo:
- n8n 2.x interessato (2.0.0–2.20.6, 2.21.0–2.22.0): i task runner sono predefiniti/obbligatori (
N8N_RUNNERS_ENABLEDè deprecato →SAFE_TO_REMOVE), quindi l'intera catena RCE è configurazione predefinita.- n8n 1.123.x interessato (questa immagine lab): i task runner sono disabilitati per impostazione predefinita, quindi il lab imposta
N8N_RUNNERS_ENABLED=trueper rispecchiare il valore predefinito della 2.x.N8N_RUNNERS_TASK_TIMEOUTè abbassato solo per far scattare più velocemente la rigenerazione del runner bloccato — non è richiesto per il bug.
docker compose -f lab/docker-compose.yml up -d # n8nio/n8n:1.123.42, runner abilitati per rispecchiare la 2.x
python3 exploit.py http://127.0.0.1:5678 -c "id; hostname"
# l'output del comando appare sull'host 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 usa solo la libreria standard di Python e guida l'API REST di n8n end-to-end
(setup/login dell'owner → deploy dei flussi di lavoro → innesca la catena).
Osservato:
[*] 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) è l'account di servizio del runner e l'output è lo stato live di id/uname
— esecuzione genuina, non eco dell'input.
Qualsiasi utente autenticato che può creare o modificare un flusso di lavoro ottiene l'esecuzione di comandi del sistema operativo sull'host n8n, sfuggendo alla sandbox del nodo Code — compromissione totale del server di automazione e di ogni credenziale/sistema che può raggiungere.
Object.create(null), uccidendo la scrittura __proto__).--disable-proto=delete /
--disallow-code-generation-from-strings, ma questi proteggono il runner, non il
processo principale dove atterra l'inquinamento.Segnalare i flussi di lavoro i cui parametri di paginazione della Richiesta HTTP usano
valori type di __proto__ / constructor / prototype, e NODE_OPTIONS che appare
come errore di proprietà dell'entità nei log di n8n/TypeORM.
Vedere ANALYSIS.md per la primitiva, il comportamento dell'ambiente
for…in di Node, il ciclo di vita del runner e la patch.