Home > Blog > Desenvolvimento Web
Desenvolvimento Web
JavaScript
Programação

Prototype Pollution no Node.js

Atualizado em: 30 de agosto de 2026

Estação de trabalho usada no desenvolvimento de aplicações Node.js

Prevenir Prototype Pollution no Node.js é importante em aplicações que mesclam objetos, interpretam caminhos dinâmicos, processam JSON ou permitem que usuários definam configurações. O ataque modifica propriedades herdadas de objetos JavaScript por meio de chaves como __proto__, constructor e prototype.

Quando uma biblioteca ou função copia dados sem validar essas chaves, um valor controlado pelo atacante pode aparecer em objetos que nunca receberam a propriedade diretamente. Isso pode alterar validações, opções de segurança, comportamento de templates, autorização ou fluxo de execução.

Neste guia, você aprenderá como o ataque funciona, quais padrões são vulneráveis, como bloquear chaves perigosas, usar objetos sem protótipo, validar input, atualizar dependências e testar bypasses.

O que é Prototype Pollution?

Prototype pollution é a alteração indevida de protótipos compartilhados. A referência da OWASP sobre Prototype Pollution explica o risco. A documentação da MDN sobre a cadeia de protótipos ajuda a entender o modelo.

Cadeia de protótipos

Objetos JavaScript normalmente herdam de Object.prototype:

const user = {};
console.log(Object.getPrototypeOf(user) === Object.prototype);

Quando uma propriedade não existe diretamente, o runtime procura na cadeia.

Exemplo de impacto

const options = {};

if (options.isAdmin) {
  grantAdminAccess();
}

Se Object.prototype.isAdmin foi poluído, o teste pode retornar verdadeiro mesmo sem propriedade própria.

Merge vulnerável

function merge(target, source) {
  for (const key in source) {
    if (typeof source[key] === 'object') {
      target[key] ??= {};
      merge(target[key], source[key]);
    } else {
      target[key] = source[key];
    }
  }

  return target;
}

Uma entrada com caminhos especiais pode alcançar protótipos.

Payload conceitual

{
  "__proto__": {
    "isAdmin": true
  }
}

O comportamento exato depende do parser, da forma de cópia e da versão das bibliotecas, mas a chave deve ser tratada como perigosa.

constructor.prototype

Bloquear apenas __proto__ não basta. Um caminho como:

constructor.prototype.isAdmin

pode atingir o protótipo em funções que interpretam caminhos separados por ponto.

Função set vulnerável

function setByPath(object, path, value) {
  const parts = path.split('.');
  let current = object;

  for (const part of parts.slice(0, -1)) {
    current[part] ??= {};
    current = current[part];
  }

  current[parts.at(-1)] = value;
}

Se path vem do usuário, ele pode atravessar chaves proibidas.

Bloqueie chaves perigosas

const forbiddenKeys = new Set([
  '__proto__',
  'prototype',
  'constructor'
]);

function assertSafeKey(key) {
  if (forbiddenKeys.has(key)) {
    throw new Error('Chave não permitida');
  }
}

A validação deve ocorrer em todos os níveis e em cada segmento de caminho.

Allowlist é melhor

Quando o formato é conhecido, aceite apenas campos esperados:

const allowed = new Set([
  'displayName',
  'timezone',
  'language'
]);

Uma allowlist reduz ataques e erros de mass assignment.

JSON Schema ou Zod

Valide a estrutura antes de copiar:

const SettingsSchema = z.object({
  displayName: z.string().max(100),
  timezone: z.string(),
  language: z.enum(['pt', 'en', 'es'])
}).strict();

O modo estrito rejeita propriedades desconhecidas. O artigo sobre Zod será publicado nesta mesma fila.

Object.hasOwn()

Ao verificar flags, use propriedade própria:

if (
  Object.hasOwn(options, 'isAdmin')
  && options.isAdmin === true
) {
  grantAdminAccess();
}

Isso reduz o impacto de propriedades herdadas, mas não corrige a origem da poluição.

Object.create(null)

const dictionary = Object.create(null);
dictionary.userId = '42';

Esse objeto não herda de Object.prototype e é útil para dicionários. Entretanto, ele não possui métodos como hasOwnProperty.

Map

Quando as chaves são dinâmicas, prefira Map:

const values = new Map();
values.set(userControlledKey, value);

Map não utiliza a cadeia de protótipos para armazenar entradas.

Object.assign

Object.assign é mais previsível que merges recursivos improvisados, mas ainda é necessário validar a entrada e entender setters especiais.

const safe = Object.assign({}, validatedInput);

Spread de objeto

const settings = {
  ...validatedSettings
};

O spread não substitui validação. Chaves desconhecidas ainda podem alterar o comportamento da aplicação.

Desestruturação e defaults

const {
  timeout = 3000,
  retries = 0
} = validatedOptions;

Crie um objeto novo apenas com campos aceitos, em vez de repassar todo o input.

Mass assignment

Prototype pollution e mass assignment aparecem juntos quando a API copia o corpo inteiro para o modelo:

await users.update(userId, req.body);

Selecione campos explicitamente e não aceite role, isAdmin ou propriedades internas.

Configurações de segurança

Não dependa de defaults em objetos não confiáveis:

const secure = options.secure !== false;

Mesmo essa lógica pode ser afetada quando options possui herança inesperada. Valide e crie um objeto limpo.

Bibliotecas vulneráveis

Funções de merge, clone, query string e set por caminho já tiveram falhas desse tipo. Mantenha dependências atualizadas e acompanhe advisories.

Consulte Segurança de Dependências no Node.js quando o artigo entrar na fila.

npm audit

Use scanners como uma camada, não como substituto de revisão. Uma biblioteca pode ser vulnerável apenas em determinado uso.

Dependências transitivas

O pacote vulnerável pode estar vários níveis abaixo. Use lockfile, overrides quando necessário e atualizações testadas.

Query parsers

Parâmetros como:

?__proto__[isAdmin]=true

podem ser interpretados por parsers extensos. Configure profundidade, quantidade de parâmetros e bloqueio de protótipos.

Express e frameworks

Verifique a configuração do parser usado por body, query e form data. Não assuma que todo objeto recebido é seguro.

Template engines

Propriedades poluídas podem alterar opções de escape, caminhos e helpers. Mantenha templates e dados separados.

RCE

Prototype pollution nem sempre executa código diretamente, mas pode formar uma cadeia com um “gadget” vulnerável. Um gadget é uma parte da aplicação que usa uma propriedade herdada de maneira perigosa.

Exemplo de gadget

spawn(command, args, {
  shell: options.shell
});

Se options.shell vier de protótipo poluído, o comportamento pode mudar. Não use input não confiável em APIs de execução.

Command injection

Mesmo sem pollution, evite shell e valide argumentos. Consulte práticas de processos filhos e segurança de comandos.

Isolamento de autorização

Não baseie autorização em objetos genéricos mesclados. Use identidade validada e consultas explícitas.

Veja RBAC no Node.js e ABAC no Node.js.

Multi-tenancy

Uma propriedade herdada de tenant não deve decidir acesso. O tenant precisa vir da identidade autenticada. Consulte Multi-Tenancy no Node.js.

Congelando protótipos

Object.freeze(Object.prototype);

Essa medida pode quebrar bibliotecas e não deve ser aplicada sem testes. Ela também não substitui validação.

Flags do Node.js

Versões do Node.js podem oferecer opções para controlar __proto__. Verifique a documentação da versão usada, mas continue bloqueando chaves perigosas no código.

Processos isolados

Plugins e transformações não confiáveis devem rodar em isolamento real, com permissões limitadas. Prototype pollution em um processo privilegiado pode afetar muitas requisições.

Reinício após incidente

Se um protótipo global foi alterado, remover a propriedade pode não ser suficiente para restaurar todo o estado. Reinicie processos depois de corrigir e investigar.

Logs

Registre rota, campo bloqueado, request ID e tenant, sem armazenar payload completo. Consulte Logs com Pino no Node.js.

Auditoria

Tentativas contra campos de privilégio e alterações de configuração devem ser auditadas. Veja Logs de Auditoria no Node.js.

Teste básico

test('rejeita __proto__', () => {
  assert.throws(() => {
    parseSettings(JSON.parse(
      '{"__proto__":{"isAdmin":true}}'
    ));
  });

  assert.equal({}.isAdmin, undefined);
});

Teste de constructor.prototype

Teste caminhos como:

constructor.prototype.polluted
prototype.isAdmin
__proto__.enabled

Teste de objetos aninhados

As chaves podem aparecer em qualquer profundidade. Gere payloads recursivos e confirme rejeição.

Teste de query string

Envie variações com colchetes, pontos, encoding e parâmetros repetidos.

Teste de dependência

Quando atualizar uma biblioteca de merge ou parser, mantenha um teste de regressão com o payload que antes era vulnerável.

Teste de autorização

Defina uma propriedade em Object.prototype no ambiente de teste e confirme que verificações usam campos próprios e identidade válida.

Monitoramento

Alerte sobre chaves proibidas, aumento de erros de parser e tentativas repetidas. Não confie apenas em bloqueio por WAF.

Erros comuns

  • Bloquear apenas __proto__: constructor.prototype permanece.
  • Usar for…in: propriedades herdadas são percorridas.
  • Merge recursivo próprio: caminhos especiais alcançam protótipos.
  • Confiar no scanner: o uso vulnerável continua.
  • Autorizar por propriedade herdada: privilégios podem mudar.
  • Copiar req.body inteiro: mass assignment aparece.
  • Corrigir sem reiniciar: estado poluído pode persistir.

Boas práticas

  • Use allowlists de campos.
  • Bloqueie __proto__, prototype e constructor.
  • Valide cada segmento de caminho.
  • Prefira Map para chaves dinâmicas.
  • Use Object.create(null) em dicionários.
  • Verifique propriedades próprias.
  • Atualize dependências.
  • Evite merges genéricos.
  • Teste payloads codificados.
  • Reinicie após incidente.

Conclusão

Prevenir Prototype Pollution no Node.js exige controlar como dados externos viram propriedades. Chaves especiais, caminhos dinâmicos e merges recursivos não podem ser tratados como operações comuns.

Allowlists, objetos limpos, Map, validação estrita e dependências atualizadas reduzem o risco. Quando autorização e configurações usam apenas propriedades próprias e dados validados, uma entrada maliciosa deixa de alterar silenciosamente o comportamento global da aplicação.

10 melhores cursos de programação em 2026

Descubra os melhores cursos de programação. Aprenda a escolher o curso ideal para iniciar ou avançar na carreira de desenvolvedor

POSTS RELACIONADOS

Ver todos

Seta para a direita