Skip to main content

Segurança do npm: como prevenir ataques à cadeia de suprimentos

Escrito por
blog npm security meme

8 de novembro de 2022

0 minutos de leitura

A segurança do npm tem sido um tema recorrente na mídia nos últimos anos, principalmente em referência aos pacotes npm disponíveis no ecossistema, e não ao próprio registro do npm. O aumento dos riscos de segurança para desenvolvedores e para o software que criamos torna ainda mais importante saber como prevenir ataques à cadeia de suprimentos e outras vulnerabilidades relacionadas ao ciclo de vida do desenvolvimento de software.

Os ataques à cadeia de suprimentos assumem várias formas, como ataques de confusão de dependências, inserção de backdoors com código malicioso em pacotes de código aberto e comprometimento da infraestrutura de pipelines de build. Os riscos de segurança presentes nas bibliotecas e nos ecossistemas de código aberto representam uma ameaça iminente para os desenvolvedores.

Quais controles de segurança de software podemos adotar para melhorar nossa postura de segurança? Neste post, quero apresentar práticas de segurança do npm e ferramentas de segurança da cadeia de suprimentos disponíveis para você, desenvolvedor JavaScript (e também para quem desenvolve em TypeScript).

O cenário atual dos ataques à cadeia de suprimentos em 2022

Há cada vez mais evidências de que os desenvolvedores têm um papel fundamental na segurança de aplicações. Eles estão diretamente envolvidos no aumento dos incidentes de segurança, como o módulo peacenotwar, o ataque de confusão de dependências contra gmx-reference e os ataques de confusão de dependências do Cobalt Strike — apenas alguns exemplos das notícias recentes sobre segurança do npm.

Quantas dependências npm e quantos mantenedores diferentes você estima ter nos seus projetos? Uma pesquisa acadêmica publicada em 7 de junho de 2019 trouxe algumas informações interessantes sobre esse tema:

A instalação de um pacote npm médio implica confiar em 79 pacotes de terceiros e 39 mantenedores, criando uma superfície de ataque surpreendentemente ampla.

Agentes maliciosos que se dedicam a ataques à cadeia de suprimentos estão sempre encontrando formas inovadoras de se infiltrar em um ecossistema e infectá-lo com malware. Um ótimo exemplo disso é um artigo recente de pesquisa em segurança, publicado em 2021, que descobriu:

2.818 endereços de e-mail de mantenedores associados a domínios expirados, permitindo que um invasor assumisse 8.494 pacotes ao tomar controle das contas npm. […] Descobrimos que 58,7% dos pacotes e 44,3% dos mantenedores estão inativos no registro do npm.

Apesar dessas preocupações, o software de código aberto contribui muito para diversas tecnologias inovadoras e ajuda as organizações a acelerar a entrega de resultados de negócios. Por mais que o código aberto seja um grande presente para o mundo, é importante chamar a atenção para os riscos associados às bibliotecas de software de terceiros e para o alcance dos danos que elas podem causar quando incluídas nas nossas aplicações.

O que é segurança da cadeia de suprimentos?

É bastante comum que desenvolvedores pensem na segurança da cadeia de suprimentos de software de código aberto em termos de pontos de contato imediatos, como os pacotes npm que instalamos (npm install event-stream) e importamos para nossos projetos (import colors). No entanto, a superfície de ataque da cadeia de suprimentos de software é muito mais ampla.

O que é um ataque à cadeia de suprimentos?

No contexto do desenvolvimento de software, um ataque à cadeia de suprimentos ocorre quando agentes maliciosos inserem malware, backdoors ou outros tipos de payloads de ataque em componentes de software ou na infraestrutura relacionada ao software, que depois são usados para produzir outros softwares funcionais. Em outras palavras, agentes maliciosos não exploram diretamente nem pesquisam vulnerabilidades no código-fonte do software visado.

Os níveis da cadeia de suprimentos para artefatos de software, conhecidos pela sigla SLSA, são uma boa referência para identificar pontos frágeis de integração onde há riscos. Veja a representação visual abaixo, adaptada do white paper da Snyk sobre segurança da cadeia de suprimentos:

Diagrama da cadeia de suprimentos de software com as etapas de código-fonte, compilação, pacote, dependência e uso, destacando riscos à integridade do código-fonte e da compilação.

Se voltarmos aos fundamentos de como criamos software hoje, fica claro que muitos desses marcos conhecidos da produção de software estão sujeitos a ameaças bem reais. Na verdade, o artigo a seguir, publicado no blog de segurança do Google, apresenta vários exemplos de incidentes de segurança reais para cada um desses marcos da entrega de software.

Segurança do npm e medidas preventivas para proteger a cadeia de suprimentos

(1) Previna a injeção em arquivos de lock do npm

Em setembro de 2019, divulguei uma pesquisa de segurança que detalhava os riscos inerentes à segurança dos arquivos de lock no ecossistema npm. Descobriu-se que os dois gerenciadores de pacotes JavaScript, Yarn e npm, eram vulneráveis.

A ameaça à segurança surge quando agentes maliciosos obtêm acesso e permissão para contribuir com alterações no código-fonte, por meio de mecanismos como pull requests, usados com frequência no GitHub para contribuir com projetos de código aberto. Nesse contexto, se eles alterarem um arquivo de lock, como package-lock.json ou yarn.lock, para incluir uma nova dependência de pacote npm — ou até mesmo apenas modificarem a URL de origem de um pacote existente —, qualquer execução da instalação de pacotes (como npm install ou yarn install) buscará o malware.

A captura de tela a seguir mostra como injetei um pacote malicioso por meio de uma pull request que fiz em um pacote de código aberto no npm. Observe com atenção e veja se consegue identificá-lo:

Solicitação de pull request no GitHub mostrando atualizações de dependências em package.json, incluindo alterações de versão de @octokit/rest e @types/node

Talvez você esteja seguindo esta lista de verificação:

  • Os pacotes que introduzi são conhecidos no ecossistema e não têm vulnerabilidades — certo.

  • Não há tentativas de typosquatting nesses nomes de pacotes — certo.

  • Estas são versões válidas desses pacotes, que não são maliciosas por si só — certo.

Mesmo assim, seria muito difícil encontrá-lo.

Tente expandir o arquivo yarn.lock. O arquivo de lock de um gerenciador de pacotes é gerado por máquina e não foi feito para ser lido ou mantido por pessoas. Isso significa que é muito difícil encontrar alterações como a atualização maliciosa que injetei nele:

Diferença de código no GitHub mostrando uma dependência do arquivo de lock do npm sendo alterada de registry.npmjs.org/ms para github.com/lirantal/ms/tarball/master

Além disso, os gerenciadores de pacotes JavaScript permitem instalar pacotes de origens bastante incomuns, como um gist do GitHub ou diretamente de um repositório de código-fonte.

Isso significa que posso simplesmente atualizar o arquivo de lock para especificar uma nova origem (por exemplo, na chave resolved) que eu, como invasor, controlo totalmente. Também posso definir o valor de integridade SHA512 correspondente para evitar que qualquer alerta seja acionado.

Se essa pull request for aprovada, o próximo colaborador do projeto que executar yarn install receberá meu pacote malicioso. Mas, na verdade, nem é preciso esperar que um desenvolvedor interaja com o projeto — muitos projetos de código aberto maduros têm um ambiente de integração contínua (CI).

É muito provável que, assim que essa pull request for criada, um build automatizado seja iniciado no GitHub Actions (ou talvez no CircleCI). Ao compilar essa pull request, ele executará o processo de instalação do npm, o que pode me permitir acessar e roubar informações confidenciais, como secrets do ambiente de CI, chaves de API etc.

Para prevenir esse tipo de ataque de injeção em arquivos de lock, você pode usar o lockfile-lint. Com o lockfile-lint, os desenvolvedores podem analisar os arquivos de lock e garantir que esses registros das dependências fixadas, gerados por máquina, sigam uma política de confiança específica.

Por exemplo, você pode configurar o lockfile-lint para falhar se encontrar no arquivo de lock yarn.lock fontes de pacotes npm que não sejam o espelho oficial yarnpkg.org:

Terminal mostrando o npx lockfile-lint verificando o yarn.lock e informando um host inválido para o pacote ms.

Resumindo, estas são as medidas preventivas para esse possível risco de segurança:

  1. Use o lockfile-lint no seu ambiente de desenvolvimento e na sua CI.

  2. Não permita que colaboradores externos atualizem o arquivo de lock.

  3. Use bots automatizados para atualizar dependências. Eles atualizam o arquivo de lock de forma automatizada e confiável (buscando os pacotes no registro oficial). Dica: o bot da Snyk faz isso.

(2) Previna a execução arbitrária de comandos

Tomara que não seja novidade para você que executar o comando npm install pode fazer com que os pacotes instalados executem comandos arbitrários durante o processo de instalação.

Se você tivesse executado o comando a seguir em um determinado momento:

npm install @vue/cli

Você teria sido vítima da sabotagem do pacote npm node-ipc, em que o mantenedor modificou o pacote para executar comandos que criam arquivos na pasta da sua área de trabalho. Em outras versões do pacote npm node-ipc, ele seguiu um caminho mais nocivo, incluindo uma versão que percorre os arquivos do seu disco rígido e os apaga por completo.

As dependências podem executar comandos arbitrários porque o gerenciador de pacotes npm oferece um hook de ciclo de vida de instalação como parte do processo de instalação de uma dependência. Na maioria das vezes, os mantenedores de módulos o usam por motivos legítimos, como preparar artefatos de instalação, buscar arquivos de recursos ou limpar arquivos após a instalação. Ele também pode ser usado para compilação cruzada de bindings de módulos nativos que precisam ser compilados no ambiente em que são instalados.

A pesquisa anterior, O que são pontos fracos na cadeia de suprimentos do npm?, que mencionamos, traz algumas informações sobre o uso de hooks de instalação no ecossistema:

Descobrimos que 2,2% (33.249) dos pacotes usam scripts de instalação, o que indica que 97,8% dos pacotes podem estar seguindo a recomendação do npm de não usar scripts de instalação como prática recomendada de segurança.

Veja as medidas preventivas que você deve adotar:

  1. Adicione a flag --ignore-scripts ao comando npm install para impedir a execução de comandos arbitrários pelas dependências de pacotes npm.

  2.  Considere adicionar essa flag ao arquivo de configuração .npmrc do seu projeto para proteger também os outros desenvolvedores que trabalham com você.

  3. Lembre-se de que habilitar essa flag pode impedir a execução de alguns casos de uso legítimos dos pacotes npm e causar falhas no processo de instalação.

  4. Observe que a flag --ignore-scripts não se aplica a pacotes instalados de origens de arquivos locais nem à instalação de pacotes npm de um repositório Git.

(3) Evite atualizar pacotes npm sem verificar

Alguns desenvolvedores podem atualizar explicitamente todas as dependências para as versões mais recentes como parte de um processo de integração contínua (CI). Eles fazem isso, junto com a execução de testes e casos de teste de fluxo bem-sucedido, para garantir que as aplicações continuem funcionando como esperado mesmo quando as dependências lançam novas versões. É uma forma de garantir compatibilidade futura sem comprometer o funcionamento da aplicação.

Você pode atualizar para a versão mais recente do npm executando:

npm update

Ou usando o pacote npm específico para isso:

npx npm-check-updates -u

Embora os motivos para fazer isso sejam legítimos, essas chamadas atualizações de pacotes npm sem verificação também podem abrir caminho para incidentes de segurança. Como acabamos de ver, executar npm install pode ser bastante perigoso.

Atualizar dependências sem verificar envolve o risco inerente de expor você a ameaças desnecessariamente, como ataques de confusão de dependências, além de outros incidentes que vimos nos últimos anos, como o incidente de segurança do colors e o incidente de segurança do node-ipc.

Adote estas medidas preventivas para manter suas dependências atualizadas:

  1. Use uma ferramenta inteligente e automatizada para atualizar dependências. Muitas dessas ferramentas já incluem recursos para atualizar automaticamente manifestos e arquivos de lock.

  2. Revise cuidadosamente o changelog e os artefatos de lançamento. Verifique se houve mudança de mantenedor ou se há um bom motivo para isso. Confira também se a versão recém-lançada tem o código-fonte correspondente disponível.

É especialmente importante destacar as atualizações automatizadas e o risco de baixar um pacote npm malicioso. A Snyk não recomenda atualizar para versões lançadas há menos de 21 dias. Isso evita versões que introduzem bugs funcionais e são removidas posteriormente, ou versões publicadas por uma conta comprometida (quando o titular perde o controle para alguém com intenções maliciosas).

Saiba mais sobre isso na documentação da Snyk sobre atualização de dependências com PRs automatizados.

(4) Evite a confusão de dependências

A confusão de dependências é um tipo de ataque no qual um pacote é criado e usado internamente por uma organização, em algum proxy privado ou serviço de hospedagem, mas o nome desse pacote continua disponível para registro no npm.

Infelizmente, é fácil configurar as ferramentas locais de forma incorreta. Isso, junto com a maneira como o gerenciador de pacotes npm busca pacotes, cria a seguinte situação: quando existe um pacote com o mesmo nome no registro público do npm e uma versão mais recente do que a instalada, o npm instala essa versão.

Se um funcionário da Microsoft não tivesse tomado cuidado, ou se o gerenciador de pacotes npm e o servidor proxy interno estivessem configurados incorretamente, isso poderia ter resultado em um ataque de confusão de dependências capaz de se infiltrar na infraestrutura interna da empresa:

npm install azure-core-tracing-samples-js

Na verdade, o mesmo risco se aplica ao caso em que os funcionários executavam este comando confiável de atualização de pacotes npm:

npm update

A razão está na maneira como funcionam os ataques de confusão de dependências. Embora isso tenha sido divulgado em uma pesquisa pública em 2021, a equipe de pesquisa de segurança da Snyk encontrou evidências de agentes de ameaças atacando a infraestrutura de empresas com esse método. Além disso, Nishant Jaint, pesquisador de segurança, descreveu outro possível vetor de ataque para a confusão de dependências por meio de aliases de pacotes npm.

Para detectar e evitar ataques de confusão de dependências, você pode usar a ferramenta gratuita e de código aberto da Snyk chamada snync. Veja um exemplo de execução:

npx snync --directory ~/my-app --private “superlaser”

Testing project at ~/my-app
Reviewing your dependencies...

Checking dependency: death-star-hyper-reactor
   -> ⚠️ vulnerable
Checking dependency: superlaser
   -> ❌ suspicious

(5) Segurança do NPM: proteção proativa contra malware

Você provavelmente já executou o comando npm install para instalar um pacote npm e se deparou com uma saída como esta:

$ npm install amp-html

added 1166 packages from 1172 contributors and audited 39128 packages in 112.505s
found 93911 high severity vulnerability

O problema é que, embora tenham sido encontradas vulnerabilidades de segurança nos pacotes, você só ficou sabendo delas depois de instalar os pacotes vulneráveis. O que o pacote npm amp-html contém? Antes de falar sobre isso, vamos conhecer uma maneira melhor de instalar pacotes e retomar o controle.

Imagine poder coletar informações úteis sobre a integridade dos pacotes das suas dependências antes de instalá-las e, então, tomar uma decisão consciente: adicionar o pacote npm ao seu projeto ou continuar procurando no npmjs.org.

Quero apresentar a você um projeto de código aberto que criei, chamado npq. Com o npq, você pode implementar medidas de segurança proativas e decidir com mais informações se deve instalar um pacote, com base em sinais como a existência de um README, uma licença compatível e um repositório associado no GitHub.

A ferramenta npq também tem integração completa com a Snyk. Se você criar uma conta gratuita, ela detectará o token da API da Snyk disponível no seu ambiente e consultará a Snyk Vulnerability Database.

No meu ambiente de desenvolvimento, criei um alias para que o comando npm execute npq, assim:

alias npm=npq

Agora, sempre que executo npm install, na verdade o npq é chamado para fazer as verificações e validações primeiro. Se eu decidir continuar, o npq devolve o controle à CLI do gerenciador de pacotes npm para prosseguir com a instalação:

Janela do terminal mostrando um projeto na versão 1.0.0 usando Node.js v12.16.1, com um comando npm sendo digitado.

Você também pode instalar o npq e usá-lo pontualmente para verificar vulnerabilidades:

npq install amp-html

Outra forma de avaliar a integridade das dependências de pacotes npm é usar o Snyk Advisor, uma ferramenta prática para pesquisar pacotes npm, alternativas e pacotes semelhantes, além de consultar informações sobre o status de manutenção e segurança deles.

Veja como o pacote npm amp-html aparece no Snyk Advisor:

Página do Snyk Advisor para o pacote npm amp-html, com nota de saúde de 24/100, problemas de segurança, popularidade limitada e manutenção inativa.

(6) Ataques Trojan Source

Ataques Trojan Source é o nome de um artigo de pesquisa de segurança publicado recentemente que investigou o risco de vulnerabilidades causadas por código-fonte invisível.

Veja este trecho de código de um projeto Node.js de back-end, que lida com parte do acesso administrativo provisionado a uma rota específica. Ele contém um ataque Trojan Source. Você consegue identificar o problema?

module.exports = fastify => (req, res, next, access = {}) => {
 if (!req.query.token && access.login) { // Redirect to login
   return fastify.login.view(req, res);
 }

 // Debug login for issue #1812
 if (req.session.user != "user‮ ⁦// Check if admin⁩ ⁦") {
    console.log("You are an admin.");
 }

 if (!req.query.token) { // Private access without token.
   return res.code(401).send(new Error('Missing token.'));
 }

 …

Vou ativar o realce de sintaxe JavaScript na minha IDE para facilitar a visualização. Veja uma captura de tela do trecho acima:

Código JavaScript com realce de sintaxe mostrando exportações de módulos, redirecionamento para login, verificação de administrador e resposta 401 quando falta um token.

Observe a segunda condicional, relacionada a Debug login for issue #1812. Para entender melhor o que está acontecendo, vamos converter esse código em um código Node.js executável:

#!/usr/bin/env node

const accessLevel = "user";

 // Debug login for issue #1812
 if (accessLevel != "user‮ ⁦// Check if admin⁩ ⁦") {
    console.log("You are an admin.");
 }

Se você executar este código com node test.js, verá a seguinte saída:

You are an admin.

Mas por quê? Já está coçando a cabeça?

A razão é que o texto do trecho de código-fonte acima contém caracteres de controle, invisíveis à primeira vista, mas que alteram profundamente o texto real do código-fonte. Esses caracteres mudam efetivamente a ordem do texto da esquerda para a direita e da direita para a esquerda. Assim, quando um interpretador ou compilador lê o código, eles criam um significado diferente daquele percebido pelo olho humano, que não os nota.

Como os desenvolvedores podem identificar esse tipo de problema e evitar que ele passe despercebido nas revisões de código quando alguém contribui para o projeto?

Se você usa Snyk Code para monitorar seu código-fonte, a ferramenta informa quando detecta vulnerabilidades de segurança, como injeção de SQL ou injeções de código inseguras. Além disso, Snyk Code também informa sobre problemas relacionados a Trojan Source no código:

Interface do Snyk Code exibindo código JavaScript com um comentário destacado que sugere acesso exclusivo para administradores e um painel de análise de fluxo de dados

A Snyk também mostra rapidamente a vulnerabilidade Trojan Source, junto com as demais vulnerabilidades encontradas no seu projeto, na página de visão geral do projeto:

Painel de problemas do Snyk Code exibindo descobertas de TrojanSourceConfusingUnicode, filtros de gravidade, pontuações e código JavaScript destacado.

Felizmente, o Visual Studio Code e a plataforma GitHub foram atualizados com indicadores visuais que alertam quando você visualiza arquivos de código-fonte com caracteres de controle invisíveis.

Outra ferramenta de código aberto que ajuda a detectar e mitigar ataques Trojan Source em bases de código JavaScript com o ESLint é o plugin do ESLint eslint-plugin-anti-trojan-source.

Segurança do NPM: o que vem a seguir?

Os riscos à segurança da cadeia de suprimentos de software não param de crescer. E, como se isso não bastasse, os ataques estão cada vez mais sofisticados e têm como alvo os desenvolvedores e seus ecossistemas.

Recomendo muito a leitura destes artigos para você conhecer melhor as práticas recomendadas de segurança do npm e outros tópicos relacionados:

  1. Como evitar pacotes maliciosos e ataques à cadeia de suprimentos com a Snyk, por Daniel Berman.

  2. O que é um backdoor? Vamos criar um com Node.js, por Ulises Gascón

  3. 10 práticas recomendadas de segurança do npm, por Liran Tal e Juan Picado.

Se você quer aprender práticas de programação segura, também recomendo fazer uma das aulas práticas sobre segurança em JavaScript no Snyk Learn. Elas são curtas e totalmente gratuitas!

Os pacotes NPM podem ser maliciosos?

A verdade é que, infelizmente, pacotes npm podem ser maliciosos e usar diferentes técnicas, como ataques de typosquatting ou engenharia social para inserir um backdoor no event-stream, para introduzir código malicioso que é executado no ambiente do desenvolvedor. Na verdade, alguns casos de pacotes maliciosos foram atribuídos a mantenedores que sabotaram os próprios pacotes npm com código malicioso, como no caso do node-ipc.

Como verificar a segurança de pacotes NPM?

A Snyk encontra e denuncia regularmente pacotes npm maliciosos, como no caso da descoberta de mais de 200 pacotes npm maliciosos. Para verificar a segurança de pacotes npm, você pode usar o Snyk Advisor para avaliar a integridade de pacotes de código aberto ou usar a CLI da Snyk e a integração com repositórios (gratuitas) para detectar e monitorar pacotes maliciosos.

O que as vulnerabilidades do NPM podem causar?

Vulnerabilidades de segurança em pacotes npm podem afetar seu aplicativo e expor você a riscos consideráveis. Se você estiver usando uma versão sem correções do pacote npm websocket-extensions, estará vulnerável a ataques de negação de serviço por expressão regular. Da mesma forma, se estiver usando uma versão sem correções do pacote npm st para hospedar e servir arquivos estáticos no seu aplicativo Node.js, você estará vulnerável a ataques de travessia de diretório.

Adorado por desenvolvedores. Confiável para a segurança.

As ferramentas da Snyk, pensadas para desenvolvedores, oferecem segurança integrada e automatizada para atender às suas necessidades de governança e conformidade.

Leia mais

blog feature snyk container custom base recommendations
Blog

Conheça o Evo Agentic AppSec: segurança que acompanha o ritmo do desenvolvimento com IA

O desenvolvimento impulsionado por IA está avançando mais rápido do que os fluxos de trabalho de segurança tradicionais. O AppSec agentivo da Snyk usa agentes de segurança para encontrar, corrigir e verificar vulnerabilidades continuamente, enquanto as pessoas definem os critérios.

blog feature ai violet
Blog

VulnBump: agentes de IA conseguem corrigir dependências vulneráveis sem quebrar o build?

O VulnBump testa agentes de IA em 307 atualizações reais de dependências. Uma correção só é considerada válida se eliminar a vulnerabilidade e mantiver o build e os testes funcionando.

Blog

Modelos de ponta encontraram as vulnerabilidades. Só o atacante encontrou as cadeias.

A análise estática encontrou as falhas, mas só os testes de ataque em aplicações ativas provaram como elas poderiam ser encadeadas para causar invasões. Uma comparação entre Evo COS, Claude Security e Claude Code Security.