Após três anos de silêncio, uma nova vulnerabilidade de prototype pollution no jQuery volta a surgir
15 de abril de 2019
0 minutos de leituraEm 26 de março de 2019, quase três anos após a divulgação da última vulnerabilidade de segurança no jQuery, soubemos recentemente de uma nova vulnerabilidade de segurança que afeta essa mesma popular biblioteca frontend.
Essa vulnerabilidade de segurança, chamada prototype pollution, permite que invasores sobrescrevam o protótipo de um objeto de uma aplicação JavaScript. Quando isso acontece, propriedades controladas pelo invasor podem ser injetadas nos objetos e levar a uma negação de serviço, ao provocar exceções em JavaScript, ou adulterar o código-fonte da aplicação para forçar a execução do caminho de código injetado pelo invasor.
A seguir, veja uma prova de conceito do relatório original que triagei no HackerOne como parte do grupo de trabalho (WG) de segurança do Node.js:
Como mostra o código, a API extend do jQuery é usada para mesclar vários objetos recursivamente.
Embora não seja uma vulnerabilidade muito simples de explorar, ela pode afetar muitos projetos e usuários devido à popularidade do jQuery no ecossistema JavaScript. Além disso, já vimos casos reais de ataques de prototype pollution, como o que afetou o mongoose em dezembro de 2018.
Recentemente, a equipe do jQuery lançou uma correção para esse problema de segurança na versão 3.4.0, para a qual recomendamos fortemente que você atualize.
Reconstruindo uma aplicação vulnerável
Como o jQuery é uma biblioteca usada principalmente no frontend, vamos ver como uma vulnerabilidade de prototype pollution se manifesta em uma aplicação no lado do cliente.
O ataque começa com uma entrada do usuário, que permite que um invasor mal-intencionado injete um objeto que talvez o desenvolvedor não tenha sanitizado nem identificado para receber algum tratamento especial.
Imagine que você está criando uma aplicação em que um usuário pode enviar payloads JSON, que são salvos sem alterações. Talvez você queira oferecer esse recurso para que o usuário controle a estrutura do conteúdo, pela qual você não quer se responsabilizar.
Veja a seguir um exemplo de payload que seria enviado nesse caso:
Imagine que você precisa clonar um objeto recursivamente, mas não sabe exatamente como fazer isso. Se pesquisar no Google por "deep clone an object in javascript", esta resposta aparece como o principal resultado de pesquisa do Stack Overflow:
Ao ver que ela tem mais de quatro mil votos positivos e foi escolhida como a resposta correta, você pode ficar bastante tentado a copiar e colar o exemplo de cópia profunda para concluir o trabalho.
Com base na recomendação do Stack Overflow, o que você esperaria que acontecesse com este exemplo de código?
A
myObjectapresenta um exemplo em formato de string, possivelmente obtido de um campo de banco de dados.A função
JSON.parse()e a funçãoextend()do jQuery são usadas para clonar uma cópia demyObject.A nova versão clonada é chamada de
newObject.
Quando JSON.parse() processa uma propriedade chamada __proto__, como a do nosso exemplo, você poderia esperar que ela atribuísse a propriedade isAdmin como true à propriedade pai do objeto. No entanto, ela cria um objeto com esse nome de propriedade, o que substitui a capacidade de encadeamento de propriedades.
Quando esse comportamento de JSON.parse() é combinado com um recurso inseguro de clonagem profunda, também chamado de mesclagem de objetos, o valor atribuído à propriedade __proto__ acaba sendo exposto no objeto JavaScript global.
Com isso em mente, vamos imaginar o trecho de código a seguir e seu impacto para entender como funcionam os ataques de prototype pollution:
Se o objeto de usuário obtido do banco de dados não tiver um valor definido para a propriedade isAdmin, essa propriedade do objeto de usuário será essencialmente indefinida. Nesse caso, acessar a propriedade isAdmin na cláusula if exigiria consultar o objeto pai na cadeia de protótipos do objeto user. Esse objeto seria Object, que agora foi contaminado e inclui a propriedade isAdmin definida como true. Como resultado, mesmo sem essa intenção por parte do desenvolvedor, o usuário passa a ser administrador e pode causar grandes danos à aplicação.
Explorando outros vetores de ataque
Como vimos no exemplo anterior, uma operação insegura de mesclagem recursiva, combinada com o funcionamento de JSON.parse, pode contaminar a cadeia de protótipos.
No entanto, essa não é a única maneira de alterar o protótipo. Veja o código a seguir:
Neste exemplo:
Um novo
myObjé criado.A cadeia de protótipos é acessada por meio de
__proto__, e esse objeto é modificado para incluir uma nova propriedade do tipo string.
Como o protótipo de myObj é, na verdade, um Object JavaScript que modificamos, todos os novos objetos criados a partir de agora também incluirão essa propriedade. Isso acontece por causa do funcionamento do JavaScript: se não houver uma propriedade no objeto a (em newObj), o JavaScript consulta o protótipo de newObj para encontrá-la e faz isso recursivamente até percorrer toda a cadeia de protótipos. No nosso caso, essa propriedade existe e, por isso, acessá-la retorna o valor da string.
Você pode estar se perguntando como alguém poderia injetar o acesso ao objeto __proto__ da maneira que acabamos de descrever.
Vamos considerar a criação de uma aplicação que lista pacotes do npm e faz algo com eles, como exibi-los em uma interface de usuário organizada.
Provavelmente precisaríamos de uma API que enviasse uma lista de pacotes npm e o conteúdo dos arquivos package.json deles:
Você pode achar que essa API é segura porque os dados vêm diretamente do próprio npmjs e das APIs que ele oferece, ou de outro site confiável que fornece esses dados e cujo serviço de API não pertence ao invasor. Mas isso seria um engano. Como você provavelmente já percebeu, os detalhes dos pacotes, como o nome e o conteúdo de package.json, estão, em última análise, sob o controle do usuário.
Para dar continuidade a esse cenário de aplicação, imagine que o desenvolvedor queira criar um mapa a partir do array para acessar os pacotes com facilidade, sem precisar percorrer o array para obter os dados.
Em essência, o desenvolvedor poderia tentar acessar os dados desta forma:
Veja um exemplo funcional desse código:
Se criássemos objetos totalmente novos nesse momento e tentássemos acessar a propriedade toString, revelaríamos o ataque de prototype pollution.
Vulnerabilidades de prototype pollution em casos reais
O jQuery não é a única vítima desse tipo de vulnerabilidade. Só no último ano, registramos mais de 20 vulnerabilidades de prototype pollution nos ecossistemas de navegadores e Node.js. Elas aparecem tanto em bibliotecas JavaScript, como lodash, podendo afetar muitos projetos frontend devido à popularidade do lodash, quanto em bibliotecas backend do Node.js que lidam com a clonagem de objetos JavaScript, como node.extend e deep-extend.
Em 21 de fevereiro, Eran Hammer também compartilhou sua história sobre como um ataque semelhante, uma vulnerabilidade de prototype poisoning, pode afetar uma biblioteca ao vazar dados por meio do joi, um pacote popular de validação usado em muitos projetos do ecossistema. Essa vulnerabilidade também afetou o hoek, uma biblioteca utilitária do mesmo grupo de desenvolvedores, parte do framework de aplicações web hapi.
Muitas dessas vulnerabilidades de prototype pollution foram relatadas por Olivier Arteau, também conhecido como HoLyVieR, por meio de divulgação responsável ao programa HackerOne mantido pelo grupo de trabalho de segurança do Node.js. O programa existe para oferecer resposta a incidentes e tratar problemas de segurança que afetam o ecossistema JavaScript como um todo. Oliver também publicou um relatório detalhado sobre a vulnerabilidade e seu impacto, além de apresentar um caso real que afetou o projeto Node.js Ghost CMS na conferência NorthSec.
Resumo
Para concluir, siga estas medidas de mitigação e boas práticas de segurança para evitar prototype pollution:
Garanta que você esteja usando implementações seguras de mesclagem recursiva.
Considere criar objetos sem protótipo, como
Object.create(null), para evitar que fiquem vulneráveis a ataques de prototype pollution.Evite usar a notação de colchetes ao lidar com dados controlados pelo usuário e, se possível, evite usá-la por completo. Considere usar a primitiva de linguagem
Mapem estruturas baseadas em mapas.
Na Snyk, valorizamos a comunidade de segurança e acreditamos que a divulgação responsável de vulnerabilidades em pacotes open source ajuda a proteger a segurança e a privacidade dos usuários. Se você acredita ter encontrado uma vulnerabilidade em um pacote Open Source, entre em contato conosco pelo endereço https://snyk.io/vulnerability-disclosure. Nosso programa de divulgação responsável busca proteger tanto o desenvolvedor quanto o pesquisador que relata a vulnerabilidade, permitindo que os desenvolvedores se beneficiem com segurança das descobertas dos pesquisadores.
Comece a resolver desafios de capture the flag
Aprenda a resolver desafios de capture the flag assistindo sob demanda ao nosso workshop virtual introdutório.
