Detecte e impeça ataques de confusão de dependências no npm para manter a segurança da cadeia de suprimentos
13 de setembro de 2021
0 minutos de leituraEm 9 de fevereiro de 2021, Alex Birsan divulgou sua pesquisa de segurança, apropriadamente chamada de confusão de dependências. Na divulgação, ele descreve como um novo tipo de ataque à cadeia de suprimentos, que explora configurações incorretas feitas por desenvolvedores e falhas de projeto em diversos gerenciadores de pacotes de ecossistemas de software de código aberto baseados em linguagens de programação, permitiu que ele obtivesse acesso e exfiltrasse dados de empresas como Yelp, Tesla, Apple, Microsoft e outras.
Essa pesquisa de segurança chamou a atenção e levou à criação de uma nova variedade de ferramentas para ajudar as organizações a detectar se estão vulneráveis ou suscetíveis a possíveis ataques de confusão de dependências.
Neste artigo, vou apresentar a você, passo a passo, o ataque de confusão de dependências e explicar como ele afeta desenvolvedores de JavaScript e Node.js que trabalham no ecossistema npm. Também veremos uma nova ferramenta de código aberto da Snyk que ajuda a detectar possíveis riscos relacionados à confusão de dependências nos repositórios do seu próprio código-fonte: snync.
Teste rápido! Você consegue adivinhar o que significa snync? A resposta está no final...
Neste artigo, vamos conhecer as diferentes formas como esse ataque à cadeia de suprimentos acontece e detalhar como reduzir os riscos em cada uma delas:
Configuração incorreta de um registro npm privado
O registro npm privado busca as versões mais recentes
Atualizações manuais de pacotes podem introduzir versões maliciosas
Ataques de confusão de dependências afetam você?
O ataque de confusão de dependências só funciona contra organizações que dependem de bibliotecas de código-fonte internas. É muito comum gerenciar pacotes privados para proteger a propriedade intelectual. Por isso, muitas organizações usam proxies internos, caches ou serviços de hospedagem de registros privados para essa finalidade.
Se uma organização gerencia um pacote privado interno, por definição, esse pacote não estará em registros públicos nem em seus espelhos. Como o pacote privado não está listado em um registro público, qualquer pessoa pode reservar esse nome e potencialmente lançar um ataque de confusão de dependências contra você. O fato de um pacote privado poder ter o mesmo nome de um pacote público é o cerne desse ataque.
Nos ecossistemas JavaScript e Node.js, o risco de ataques de confusão de dependências diminui bastante quando você usa pacotes com escopo como um namespace reservado. Mas atenção: independentemente de usar npm ou yarn, você está vulnerável a esse ataque à cadeia de suprimentos.
Reproduzindo ataques de confusão de dependências
Para explorar na prática ataques à cadeia de suprimentos na forma de confusão de dependências, vamos fazer um tutorial prático que demonstra a vulnerabilidade e como reduzir os riscos.
A seguir está o manifesto de pacotes de um projeto conhecido internamente como Death Star. Nome ótimo, não acha?
Este código está no arquivo package.json, que também mostra as dependências do projeto. Como você pode ver, superlaser é um pacote usado como dependência. Claro que essa é uma arma ultrassecreta do Império, então ela só é publicada internamente. A Death Star precisa se deslocar pelo espaço e, por isso, também depende do pacote privado death-star-secret-hyper-matter-reactor.
Como configurar um registro npm privado
Se quiser acompanhar o tutorial, precisamos fazer um breve desvio do tema principal do artigo para configurar um registro npm privado. Com um registro privado, você pode fazer seus próprios testes e reproduzir esse ataque de confusão de dependências à cadeia de suprimentos do início ao fim.
Para isso, vamos configurar um registro npm privado interno e um servidor proxy usando o Verdaccio, um projeto de código aberto criado para essa finalidade. Se você tiver o Docker instalado, é bem simples iniciar tudo:
Se tudo der certo, o Verdaccio vai nos dar as boas-vindas:

Parabéns! Agora você tem seu próprio serviço privado de hospedagem de pacotes npm rodando localmente na porta 4873.
Vamos publicar nossos pacotes npm secretos e internos, superlaser e death-star-secret-hyper-matter-reactor. Primeiro, vamos adicionar um usuário a esse novo registro privado do Verdaccio:
Agora, o arquivo de manifesto do pacote superlaser, chamado package.json:
O manifesto do pacote death-star-secret-hyper-matter-reactor é semelhante; só muda o nome do pacote. Vamos publicá-lo no nosso registro npm privado:
Por que existe a confusão de dependências?
Há três situações principais que podem levar a esse tipo de ataque à cadeia de suprimentos de software:
Configuração incorreta no ambiente de desenvolvimento ou no servidor de testes
Publicação de versões mais recentes de pacotes no registro npm público
Possíveis falhas de projeto nos gerenciadores de pacotes
Vamos analisar cada uma dessas situações.
Configuração incorreta de um registro npm privado
Quando um desenvolvedor ou sistema de integração contínua (CI) clona o código-fonte do the-death-star project — que tem a dependência interna superlaser —, como ele obtém essa dependência?
Ao executar o comando npm install, provavelmente é preciso atender aos seguintes requisitos:
É preciso saber o URL do registro npm privado onde esse pacote interno está disponível.
É preciso ter um token ou algum tipo de credencial para acessar esse registro privado.
O primeiro passo descrito acima é onde algo pode dar errado. Para especificar um registro npm privado, é necessário fornecer explicitamente as informações de configuração do gerenciador de pacotes npm.
Agora, vamos analisar algumas situações:
O que acontece se o sistema de integração contínua não tiver o registro privado configurado?
O que acontece se você for um novo desenvolvedor entrando em um projeto existente e não tiver seguido etapas anteriores, como executar o comando
npm config set registry?O que acontece se você remover ou alterar por engano a configuração do arquivo
.npmrce deixar de incluir o registro npm privado interno?
Em qualquer um desses casos, quando a configuração personalizada do registro interno é omitida, o gerenciador de pacotes npm usa por padrão o registro público (registry.npmjs.org) e baixa os pacotes de lá.
Qualquer pessoa pode publicar pacotes no registro npm público. Portanto, se alguém mal-intencionado publicar um pacote chamado superlaser, ele será baixado e instalado no lugar do pacote interno.
Como se proteger contra a confusão de dependências no npm
O problema central é não ter a configuração correta do proxy npm privado. Se um desenvolvedor ou sistema de CI não tiver essa configuração, você poderá ficar vulnerável.
Então, o primeiro passo é: sempre garanta que um arquivo .npmrc esteja disponível ou que haja outra forma de configurar o proxy npm privado.
Em segundo lugar, você pode adotar uma abordagem proativa para detectar quando está usando pacotes privados cujo namespace não foi reservado no registro público npmjs. Criamos o snync para ajudar você. Você pode executá-lo em um servidor de CI como parte das etapas anteriores à instalação das dependências. Assim, ele pode evitar que você instale um pacote malicioso por engano.
Na captura de tela a seguir, estou executando o snync com o npx e informando o diretório atual para verificar as dependências. Também especifico que o pacote chamado superlaser é de fato um pacote privado:

Como você pode ver nos resultados, o snync identificou dois possíveis problemas de confusão de dependências:
O pacote
death-star-secret-hyper-matter-reactorestávulnerable, porque não há nenhum pacote com esse nome registrado no momento no registro público npmjs. Isso significa que qualquer pessoa pode registrá-lo e, então, iniciar um ataque de confusão de dependências.O pacote
superlaserésuspicious. Isso significa que a ferramenta detectou uma de duas situações:Esse nome de pacote apareceu primeiro no código-fonte do Git e, só mais tarde, um pacote com o mesmo nome foi publicado no registro público npmjs. Isso não significa que o pacote público no registro npmjs seja malicioso, mas é um bom motivo para investigá-lo.
O nome do pacote já existia no registro público npmjs antes mesmo de você criar um pacote privado com o mesmo nome.
O snync é um projeto de linha de comando de código aberto baseado em Node.js. Convidamos você a usá-lo no seu pipeline de segurança DevSecOps.
O registro npm privado busca as versões mais recentes
O que acontece se houver um pacote com o mesmo nome que o nosso (superlaser), publicado e disponível no registro npm público, mas com uma versão semântica maior?
Para ilustrar, a situação é a seguinte:
superlaser@1.0.0está disponível no registro npm privado https://localhost:4783superlaser@1.99.999publicado por um usuário anônimo no registro npm público, em https://www.npmjs.com/package/superlaser
Agora, a pergunta é: o que acontece quando um novo projeto é criado e solicita a instalação do pacote superlaser? Ainda não há um package.json nem um arquivo de bloqueio (package-lock.json). O desenvolvedor simplesmente começa com:
Essa instalação pode acabar incluindo uma versão maliciosa do superlaser, controlada por um invasor remoto. Mas por quê? O desenvolvedor configurou o registro npm local.
Os testes mostram que, mesmo quando há um proxy npm privado interno configurado, muitos desses proxies primeiro verificam qual é a versão mais recente disponível no registro npm público. Se houver uma versão mais nova, eles buscam no registro público a versão mais recente do pacote, segundo o versionamento semântico, e a instalam.
Vamos reproduzir essa situação com o Verdaccio. Como você pode ver abaixo, publiquei o pacote npm inofensivo superlaser no Verdaccio, que funciona como serviço interno de hospedagem de pacotes npm privados:

Agora vou mostrar como, em um diretório de um novo projeto que contém apenas o arquivo .npmrc apontando para o registro local do Verdaccio, o comando npm install do pacote superlaser busca a versão mais recente no registro npm público. Isso acontece mesmo que eu esperasse receber apenas o superlaser@1.0.0, que publiquei internamente:

Isso gera um resultado inesperado e pode colocar os usuários finais em risco.
Se quiser participar da conversa e acompanhar o tema, há uma discussão pública no projeto de código aberto Verdaccio, no GitHub, sobre esse comportamento.
Tecnicamente, o processo usado pelo Verdaccio e por outros proxies npm privados leva em conta várias variáveis, como:
A versão semântica mais recente
A data de publicação do pacote
Por exemplo, se houver uma versão semântica alta no registro público npmjs, mas um pacote com o mesmo nome e uma versão semântica mais baixa — dentro do mesmo intervalo da versão pública — for criado depois da data de publicação da versão mais alta, o Verdaccio não buscará o pacote no registro público.
Como evitar baixar o pacote errado
Configure seu proxy npm privado para nunca encaminhar solicitações aos registros públicos. Se um pacote ou uma versão não estiver disponível localmente, ele deverá ser resolvido de uma forma que não baixe pacotes indiscriminadamente de fontes não confiáveis e não verificadas.
Se estiver usando o Verdaccio, como nos exemplos deste artigo, você pode fazer isso com a seguinte configuração, localizada em /verdaccio/conf/config.yaml:
O trecho acima é o arquivo de configuração padrão do Verdaccio para a versão em contêiner Docker. No entanto, você pode localizar o comentário # DO NOT FETCH PACKAGES FROM NPMJS, que comenta na linha seguinte a opção proxy: npmjs. Isso impede que o Verdaccio busque qualquer pacote do npmjs que corresponda ao padrão definido.
Atualizações manuais de pacotes podem introduzir versões maliciosas
Neste cenário, você atualiza manualmente seus pacotes npm executando npm update ounpm install <packages>@latest para manter as versões das dependências em dia.
Ao executar esses procedimentos de atualização, o mesmo comportamento que vimos antes também acontece aqui. O comando npm update solicita ao proxy npm privado que busque a versão mais recente. Em seguida, o proxy verifica qual é a versão mais atual no registro público do npm.
Vale lembrar que, se você usa yarn, executar yarn upgrade terá o mesmo resultado: buscará pacotes potencialmente maliciosos no registro público npmjs.
Podemos demonstrar isso no cenário a seguir, começando com a versão interna superlaser@1.0.0:
Agora, tenho um arquivo .npmrc que define o registro local e aponta para o servidor Verdaccio em execução. Mesmo assim, se eu simplesmente executar npm update para atualizar todas as dependências, você verá que ele busca a versão mais recente compatível com o semver no registro público npmjs:

Como se proteger?
Em vez de atualizar pacotes npm manualmente e sem conferir, opte por atualizações automatizadas de pacotes por meio de pull requests abertos nos repositórios dos seus projetos open source. Isso também mantém o manifesto de pacotes sincronizado, como package-lock.json ou yarn.lock.
O Snyk é uma opção para automatizar gratuitamente as atualizações de pacotes npm, como mostra a captura de tela a seguir de um pull request mesclado:

Você também pode ajustar as configurações de atualização automática, por exemplo, limitando o número de pull requests que o Snyk abrirá ou ignorando por completo a atualização de pacotes específicos. Saiba mais na documentação do Snyk sobre atualização de dependências com PRs automáticos.
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.
Para concluir: recursos de segurança de aplicações
Se você chegou até aqui, merece saber a resposta ao quiz apresentado no início do artigo. Demos à ferramenta open source o nome snync, uma abreviação de So Now You’re Not Confused. Acertou?
Adotar práticas seguras, seja na hora de escrever código ou na rotina diária de segurança para desenvolvedores, é mais importante do que nunca diante dos incidentes de segurança na cadeia de suprimentos. Se você ou sua equipe trabalha regularmente com JavaScript ou Node.js, estes materiais de referência podem ser úteis: