FlawOpen

flawopen.com/Vulnerabilidades/Poluição de protótipos em JavaScript

Poluição de protótipos em JavaScript

Alta gravidade CWE-1321 Cadeia de suprimentos e runtime
Explicação simples — Um manual compartilhado

Imagine uma oficina em que cada aparelho novo consulta um manual compartilhado quando suas próprias configurações não respondem à pergunta. Se alguém escreve uma regra controlada por um invasor nesse manual, outros aparelhos podem tratá-la como padrão. Na poluição de protótipos, gravações dinâmicas inseguras podem alterar o protótipo de um objeto, às vezes o Object.prototype. O efeito não é necessariamente global; o impacto depende de código que confie depois na propriedade herdada.

Alvo:JavaScript no servidor ou no navegador que mescla chaves não confiáveis ou grava caminhos de propriedades controlados pelo usuário
Vetor:Chaves não confiáveis, como __proto__, ou um caminho constructor.prototype que alcance uma gravação insegura
Impacto:Depende de um gadget alcançável: alterações lógicas, negação de serviço, XSS ou, em alguns casos no servidor, execução de código; nada disso é automático
Correção:Valide com uma allowlist ou um schema; use Map ou dicionários sem protótipo para chaves não confiáveis

Conceitos principais e termos do subsistema

Object.prototype
Protótipo compartilhado herdado por objetos comuns. Sua poluição pode afetar muitos objetos, mas não todos os objetos JavaScript nem todos os realms.
__proto__
Acessor legado de objetos comuns que obtém ou define seu protótipo. Em texto JSON, a mesma grafia é apenas o nome de uma propriedade até outro código tratá-la de forma insegura.
Mesclagem recursiva profunda
Função que percorre chaves aninhadas e as grava em outro objeto. Torna-se perigosa quando chaves não confiáveis selecionam propriedades ligadas a protótipos ou caminhos inseguros.
CWE-1321
Modificação indevidamente controlada de atributos do protótipo de objetos. A falha é a mutação sem controle; o impacto posterior depende do comportamento alcançável da aplicação.

Fluxo de ataque passo a passo

Etapa 1

Entrada de propriedades não confiáveis

Um endpoint aceita chaves de objeto controladas pelo usuário. Apenas analisar JSON com uma propriedade própria __proto__ não altera um protótipo.

Etapa 2

Gravação dinâmica insegura

Uma mesclagem recursiva ou um setter de caminho segue essas chaves e faz gravações dinâmicas sem validar cada segmento.

Etapa 3

Alteração do protótipo do alvo ou compartilhado

Dependendo do alvo e da sequência de gravações, a operação altera o protótipo de um alvo ou alcança um protótipo compartilhado, como Object.prototype. São resultados diferentes.

Etapa 4

Um gadget determina o impacto

Um gadget posterior lê o valor herdado em uma operação sensível à segurança. O resultado depende desse gadget; não presuma bypass de autenticação ou execução de código.

Mecanismo e causa raiz

A busca de propriedades em JavaScript percorre a cadeia de protótipos. Uma mesclagem vulnerável ou um setter de caminho pode deixar chaves não confiáveis, como __proto__ ou constructor.prototype, direcionarem uma gravação a um protótipo. Um campo próprio __proto__ criado por JSON.parse não é, por si só, poluição; outro código precisa interpretá-lo por meio de uma atribuição insegura. Alterar o protótipo de um alvo afeta esse alvo; modificar Object.prototype afeta objetos comuns que o herdam. Um gadget separado precisa consumir a propriedade herdada para haver impacto de segurança.

utils.js (Mesclagem recursiva vulnerável)Vulnerável
// VULNERÁVEL: chaves controladas pelo invasor chegam às gravações recursivas
function merge(target, source) {
    for (const key in source) {
        const value = source[key];
        if (value && typeof value === 'object') {
            if (!target[key]) target[key] = {};
            merge(target[key], value);
        } else {
            target[key] = value;
        }
    }
    return target;
}
// JSON.parse cria uma propriedade própria __proto__; a mesclagem insegura é o risco.
const input = JSON.parse('{"__proto__": {"isAdmin": true}}');
merge({}, input);
utils.js (Parser de perfil com allowlist)Protegido
// PROTEGIDO: copie apenas os campos que este endpoint aceita
const PROFILE_KEYS = new Set(['displayName', 'theme']);

function parseProfile(input) {
    if (!input || typeof input !== 'object' || Array.isArray(input)) {
        throw new TypeError('profile must be an object');
    }

    const profile = Object.create(null);
    for (const key of Object.keys(input)) {
        if (!PROFILE_KEYS.has(key)) continue;
        const value = input[key];
        if (typeof value !== 'string' || value.length > 80) {
            throw new TypeError('invalid profile field');
        }
        profile[key] = value;
    }
    return profile;
}

Sequência de ataque e exploração

Engenharia defensiva e regras de prevenção

Perguntas frequentes

JSON.parse() polui o protótipo por si só?

Não. JSON.parse() cria uma propriedade própria chamada __proto__; não aciona o setter herdado. O risco surge se um merge ou uma atribuição posterior acionar esse setter. Veja JSON.parse() causa Prototype Pollution?

Object.freeze(Object.prototype) corrige o problema?

Pode impedir gravações no próprio Object.prototype congelado, mas não impede o setter de alterar o protótipo de um objeto individual nem protege todos os outros protótipos. Também pode quebrar código que estende objetos internos. Use como defesa em profundidade, não como correção do tratamento de dados.

TypeScript está protegido?

Não. Os tipos são removidos em runtime. Um valor tipado como Record<string, string> ainda pode conter uma chave __proto__; valide chaves não confiáveis e construa objetos com segurança.

Explore tópicos de segurança e análises relacionadas:Diretório completo de segurança →