JSON Toolkit

O que é injeção de __proto__

Resumo: `JSON.parse` trata `__proto__` como uma chave comum; o dano acontece em uma mesclagem recursiva que grava em `Object.prototype`, então todo objeto herda propriedades do atacante.

Avançado~5 min de leituraPublicado em 2026-10-30Atualizado em 2026-10-30
Como o prototype pollution funciona e como bloqueá-lo

Resumo da ópera: bloqueie __proto__, constructor e prototype antes de qualquer merge, mescle entrada não confiável em Object.create(null) e adicione uma asserção de protótipo ao CI; atualizar bibliotecas sozinho não basta.

Em uma frase: uma chave não confiável chamada __proto__ encontra um merge recursivo, e o merge grava no protótipo.

Uma reprodução

function merge(target, source) {
  for (const key in source) {
    if (typeof source[key] === 'object' && source[key] !== null) {
      target[key] = target[key] || {};
      merge(target[key], source[key]);
    } else {
      target[key] = source[key];
    }
  }
  return target;
}

merge({}, JSON.parse('{"__proto__": {"isAdmin": true}}'));
console.log({}.isAdmin);   // true

Nenhum objeto de negócio foi tocado. Object.prototype foi, e todo objeto agora carrega isAdmin.

Por que a culpa não é do JSON.parse

const data = JSON.parse('{"__proto__": {"a": 1}}');
Object.getOwnPropertyNames(data);                  // [ '__proto__' ]
Object.getPrototypeOf(data) === Object.prototype;  // true, intocado

__proto__ chega como uma propriedade própria comum. O perigo está inteiramente no código que depois mescla, copia profundamente ou define por caminho — _.merge, alguns padrões de Object.assign e incontáveis helpers escritos à mão do tipo set(obj, path, value).

Impacto

  • Bypass de autorização: if (user.isAdmin) se torna incondicionalmente verdadeiro
  • Corrupção de dados: campos inesperados são serializados em registros armazenados
  • Negação de serviço: objetos grandes anexados ao protótipo crescem a memória permanentemente
  • Escalonamento: combinado com engines de template ou executores de comando, isso já levou a execução remota de código

Quatro defesas

Filtre as chaves.

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

Bloquear apenas __proto__ é insuficiente: constructor.prototype chega ao mesmo lugar.

Mescle em um objeto de protótipo nulo.

const safeTarget = Object.create(null);

Sem cadeia de protótipos, __proto__ é apenas uma chave de string.

Use Object.hasOwn em vez de in. O in consulta a cadeia de protótipos.

Mantenha as dependências atualizadas. lodash, deepmerge e json-patch todos já lançaram correções para essa classe de bug. Acompanhe o npm audit.

Verifique se continua corrigido

Passe um payload contendo __proto__ e constructor pelo seu merge e depois assegure:

console.log({}.polluted);                 // undefined
console.log(Object.prototype.polluted);   // undefined

Uma asserção no CI, custo quase zero, pega regressões de refatorações posteriores.

Perguntas frequentes

Que chaves um merge deve filtrar?

Bloqueie __proto__, constructor e prototype juntos. Filtrar apenas __proto__ é insuficiente, porque constructor.prototype chega ao mesmo protótipo.

Por que Object.create(null) impede a gravação?

Um objeto de protótipo nulo não tem cadeia de protótipos, então __proto__ permanece uma chave de string comum. Ele funciona como contêiner de dados, mas não substitui o filtro de chaves nem a atualização de dependências.

Como testo prototype pollution no CI?

Envie um payload contendo __proto__ e constructor pelo seu merge e depois assegure que {}.polluted e Object.prototype.polluted sejam undefined. Uma asserção não custa quase nada e pega regressões de refatorações posteriores.

Leitura adicional