CVE-2026-44789 — n8n <1.123.43 HTTP Request pagination prototype pollution a RCE (NODE_OPTIONS runner-spawn gadget). Laboratorio + PoC automatizado, verificado e2e.
Un creador de flujos de trabajo autenticado de n8n
< 1.123.43contaminaObject.prototypeen el proceso servidor de n8n a través de la configuración de paginación del nodo de solicitud HTTP, y luego escala a ejecución remota de código al abusar de cómo n8n inicia su ejecutor de tareas, filtrando unNODE_OPTIONScontaminado en un procesonodehijo y evitando el sandbox del nodo de código.
| CVE | CVE-2026-44789 |
| Advertencia | GHSA-c8xv-5998-g76h |
| Afectado | < 1.123.43, 2.0.0-rc.0 … < 2.20.7, 2.21.0 … < 2.22.1 |
| Corregido | 1.123.43 / 2.20.7 / 2.22.1 |
| Clase | CWE-1321 (Contaminación de prototipos) → CWE-94 (RCE) |
| CVSS | 9.4 (CVSS 4.0) / Crítico |
| Autenticación | Autenticado (permiso de crear/modificar flujos de trabajo) |
| Estado | CONFIRMADO — cadena completa reproducida de extremo a extremo contra n8nio/n8n:1.123.42 |
La advertencia pública solo indica que la contaminación "combinada con otras técnicas podría llevar a RCE" — no divulga un gadget. Este repositorio documenta y automatiza una cadena concreta y verificada de contaminación de prototipo → RCE.
packages/nodes-base/nodes/HttpRequest/V3/HttpRequestV3.node.ts, modo de paginación
updateAParameterInEachRequest:
paginationData.request[parameter.type]![parameterName] = parameterValue;
parameter.type, parameterName y parameterValue provienen del JSON del flujo de trabajo
(controlado por el atacante). Con parameter.type = "__proto__",
paginationData.request["__proto__"] se resuelve a Object.prototype, por lo que la
asignación escribe Object.prototype[parameterName] = parameterValue — una contaminación
global del prototipo en el proceso servidor de n8n.
La corrección usa Object.create(null) para paginationData.request, por lo que ["__proto__"]
es una clave ordinaria (con prototipo nulo) en lugar del prototipo.
packages/cli/src/task-runners/task-runner-process-js.ts inicia el ejecutor de tareas JS:
return spawn('node', [...flags, startScript], { env: this.getProcessEnvVars(...) });
normalizeSpawnArguments de Node construye el entorno hijo con
for (const key in env), que enumera propiedades heredadas enumerables.
Contaminar Object.prototype.NODE_OPTIONS = "--require=/path/evil.js" por lo tanto se filtra
en el entorno del ejecutor iniciado. El hijo es node, que respeta
NODE_OPTIONS, por lo que requiere --require el archivo del atacante al inicio → ejecución de código
fuera del sandbox del nodo de código, como el usuario del servicio n8n.
El ejecutor se inicia al arranque, pero su ciclo de vida lo reinicia cada vez que el proceso
sale (onProcessExit → start()). El atacante fuerza un reinicio después
de contaminar colgando el ejecutor con un nodo de código (timeout de tarea / OOM lo mata).
exploit.py)1. escribir /tmp/evil.js vía nodos Set → Convertir a archivo → Leer/Escribir archivos
2. colgar el ejecutor de tareas vía un nodo de código ( while(true){} ) [ANTES de contaminar]
3. contaminar Object.prototype.NODE_OPTIONS vía el nodo de solicitud HTTP ( type="__proto__" )
4. el ejecutor colgado expira → el proceso principal reinicia 'node' → hereda NODE_OPTIONS
→ require('/tmp/evil.js') → RCE
El orden importa: la contaminación hace que NODE_OPTIONS sea una clave enumerable propia en
Object.prototype, con lo que la capa TypeORM de n8n se tropieza (for…in sobre entidades),
rompiendo la persistencia del flujo de trabajo. Por lo tanto, el ejecutor ya debe estar colgado antes de que
llegue la contaminación; el reinicio entonces toma el entorno contaminado.
Alcance de configuración (leer primero). La primitiva de contaminación de prototipos se activa en cualquier versión afectada sin cambios de configuración. El gadget RCE necesita el ejecutor de tareas, al que se llega de manera diferente según la rama:
- n8n 2.x afectado (2.0.0–2.20.6, 2.21.0–2.22.0): los ejecutores de tareas están habilitados por defecto/obligatorios (
N8N_RUNNERS_ENABLEDestá obsoleto →SAFE_TO_REMOVE), por lo que la cadena RCE completa es configuración predeterminada.- n8n 1.123.x afectado (esta imagen de laboratorio): los ejecutores de tareas están deshabilitados por defecto, por lo que el laboratorio establece
N8N_RUNNERS_ENABLED=truepara reflejar el valor predeterminado de 2.x.N8N_RUNNERS_TASK_TIMEOUTse reduce solo para que el reinicio del ejecutor colgado se active más rápido—no es necesario para el error.
docker compose -f lab/docker-compose.yml up -d # n8nio/n8n:1.123.42, ejecutores habilitados para reflejar 2.x
python3 exploit.py http://127.0.0.1:5678 -c "id; hostname"
# la salida del comando aparece en el 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 biblioteca estándar de Python y maneja la API REST de n8n
de extremo a extremo (configuración/inicio de sesión del propietario → implementar flujos de trabajo → activar la cadena).
Observado:
[*] paso 1: escribió carga útil --require a /tmp/evil.js (vía nodo Read/Write Files)
[*] paso 2: envió una tarea colgante -> el ejecutor ahora está ocupado
[*] paso 3: contaminó Object.prototype.NODE_OPTIONS = --require=/tmp/evil.js
[*] esperando a que el ejecutor colgado expire, sea reiniciado y herede NODE_OPTIONS ...
RCE uid=1000(node) gid=1000(node) groups=1000(node),1000(node)
uid=1000(node) es la cuenta de servicio del ejecutor y la salida es el estado vivo de id/uname
— ejecución genuina, no eco de entrada.
Cualquier usuario autenticado que pueda crear o modificar un flujo de trabajo obtiene ejecución de comandos del SO en el host n8n, escapando del sandbox del nodo de código — compromiso total del servidor de automatización y de cada credencial/sistema al que pueda acceder.
Object.create(null), eliminando la escritura de __proto__).--disable-proto=delete / --disallow-code-generation-from-strings,
pero esos protegen al ejecutor, no al proceso principal donde llega la contaminación.Marque los flujos de trabajo cuyos parámetros de paginación de solicitud HTTP usen valores type de
__proto__ / constructor / prototype, y NODE_OPTIONS apareciendo como un error de propiedad de entidad
en los registros de n8n/TypeORM.
Consulte ANALYSIS.md para la primitiva, el comportamiento del entorno for…in de Node,
el ciclo de vida del ejecutor y el parche.