Ataque direcionado de confusão de dependências no npm é flagrado em flagrante
Snyk Security Research Team
30 de abril de 2022
0 minutos de leituraLinha do tempo da atualização sobre o ataque
10 de maio de 2022: A CodeWhite, empresa de segurança Red Team, entrou em contato conosco pelo Twitter e assumiu a responsabilidade pelos pacotes maliciosos, explicando que faziam parte de uma simulação de ataque para seus clientes — parabéns pela elaboração do ataque!
4 de maio de 2022: A DigitalOcean informou que o IP do C2 pertence a uma empresa relacionada à segurança e que verificaria a situação e nos avisaria.
1º de maio de 2022: O npm removeu os pacotes maliciosos do registro.
A história de um malware no npm
Nos últimos anos, vimos aumentar constantemente o número de pacotes maliciosos que aparecem em diferentes ecossistemas. De modo geral, a grande maioria desses pacotes é benigna: coleta informações, mas não causa danos à máquina infectada. De vez em quando, porém, encontramos um pacote realmente malicioso, com objetivo, meios e pronto para produção — esta é a história de um deles.
Encontrando malware de verdade no npm
Como parte do foco da equipe de pesquisa de segurança da Snyk na detecção proativa de pacotes maliciosos, nosso principal objetivo é analisar e emitir alertas sobre pacotes maliciosos o mais rápido possível depois que aparecem no registro de um ecossistema. Para isso, desenvolvemos um sistema robusto com vários mecanismos para detectar pacotes maliciosos.
Pouco depois de montarmos nossa armadilha para pacotes maliciosos, muitas bibliotecas já haviam sido sinalizadas como maliciosas. No entanto, ao investigar os resultados, descobrimos que muitos dos relatos eram sobre pacotes “levemente maliciosos”.
Definimos como “levemente malicioso” um pacote que faz uma das seguintes coisas:
Exfiltra informações da máquina por meio de consultas DNS (sem nenhuma outra ação)
Mineradores de criptomoedas (que são nocivos, mas não muito interessantes do ponto de vista de atividades maliciosas)
Pacotes “levemente maliciosos” de outros pesquisadores, usados principalmente para testes
Algumas outras variações dos itens desta lista
Por fim, encontramos um pacote muito interessante — gxm-reference-web-auth-server — e o classificamos como malicioso. Demos uma olhada rápida e percebemos que havia algo diferente, então iniciamos uma VM e analisamos o tarball.
Como o relato era sobre um pacote npm, a primeira coisa que examinamos foi o arquivo `package.json`. Como esperado, ele tinha um script post-install que chamava um arquivo JavaScript do pacote. Se você não conhece esses scripts, os scripts post-install são uma forma muito comum de executar scripts assim que o npm termina de instalar as dependências relevantes — e também uma maneira fácil para agentes maliciosos executarem scripts nas máquinas das vítimas.
Ao examinar o arquivo chamado, vimos que ele estava ofuscado. A ofuscação pode parecer uma forma sofisticada de “esconder” o código, mas, na prática, costuma ser relativamente fácil reverter (pelo menos no mundo do JavaScript).
O arquivo seguinte do pacote que chamou nossa atenção imediatamente era criptografado. Nossos sentidos de aranha começaram a formigar: era hora de investigar.
Engenharia reversa de malware — P1: O invólucro
Como dissemos, havia dois arquivos adicionais no pacote: um ofuscado e outro criptografado:
E o package.json incluía um script post-install:
Embora nossa primeira iniciativa tenha sido analisar o script chamado, também notamos as duas dependências com nomes sem sentido, ldtzstxwzpntxqn e lznfjbhurpjsqmr, que exploraremos mais adiante neste artigo.
Ao examinar o arquivo confsettingsaaa.js, encontramos o seguinte:
Para entender esse arquivo, usamos um desofuscador para torná-lo legível. Ao analisar o código, vimos que o arquivo (que mais tarde chamaremos de P1, por ser a parte 1 do malware) exfiltrava informações do sistema operacional e do pacote por meio de consultas a subdomínios falsos de pkgio[.]com:
Embora esse código apenas exfiltre dados e alguém possa achar que não causa danos, essas exfiltrações são, na verdade, valiosas para o reconhecimento feito pelo invasor, permitindo que ele identifique o alvo e sua configuração. Aliás, os agentes maliciosos abusam de consultas DNS porque esse tipo de consulta costuma passar por firewalls e filtros de rede, permitindo que eles acessem seus próprios servidores DNS (para a consulta) e registrem as solicitações.
A análise ficou mais interessante à medida que continuamos lendo o arquivo. Primeiro, todos os blocos try/catch chamavam uma função `fail` caso capturassem uma exceção. Depois, vimos que o script usava uma de suas dependências: lznfjbhurpjsqmr.
Antes de explicar as (duas) dependências, vamos analisar a função (ou mecanismo) fail. Quando chamada, ela faz duas coisas principais. Primeiro, apaga os próprios rastros. A função “fail” remove todos os arquivos relacionados ao pacote e faz a limpeza:
Embora esse mecanismo seja interessante em malwares, a etapa seguinte foi alarmante.
Depois da limpeza, a função cria arquivos isca package.json e index.js e registra a seguinte mensagem no console: “Consulte o registro privado em vez do repositório público; Equipe de segurança”
Depois de ler isso, ficamos com algumas perguntas: por que o pacote malicioso não se apaga por completo? Por que finge ser um pacote de espaço reservado legítimo, criado por uma equipe de segurança? Qual organização é o alvo deste pacote?
Embora tenhamos conseguido responder à maioria dessas perguntas, apesar de todos os nossos esforços para identificar e alertar o alvo, ainda não sabemos qual organização está sendo visada. Com essas perguntas em mente, vamos continuar.
As dependências: ldtzstxwzpntxqn e lznfjbhurpjsqmr
Como mencionado acima, o pacote instala duas dependências: ldtzstxwzpntxqn e lznfjbhurpjsqmr. Ao acessar as páginas delas no npm, vimos que ambas foram publicadas pelo mesmo mantenedor, cuja página era assim:

Foi interessante observar isso e confirmar algumas suspeitas sobre os pacotes. As próprias dependências, porém, eram apenas cópias de pacotes legítimos:
Nome do pacote | Pacote original | Finalidade |
|---|---|---|
|
| Pacote que oferece uma API mais simples para o npm install (instala coisas de forma programática). |
|
| Exige o npm global como módulo local do Node.js. |
Como confirmamos depois, ao auditar o restante do código, esses pacotes eram usados exatamente como os pacotes originais deveriam ser. Por que o agente malicioso se esforçou para criar esses pacotes em vez de usar os originais? Não sabemos e provavelmente nunca saberemos.
Filtragem de vítimas, registros privados e transição para P2
Algumas observações rápidas…
Na seção a seguir, sempre que dissermos “o código interrompe a execução”, queremos dizer que a função
failmencionada acima foi chamada para fazer a limpeza.Como não sabemos qual organização está sendo visada, vamos chamá-la de
ORG.
A próxima etapa de P1 é buscar as configurações de autenticação dos registros privados no arquivo .npmrc (é aqui que lznfjbhurpjsqmr é usado). Se não encontrar essas configurações ou o arquivo (o código faz uma busca em toda a máquina), interrompe a execução.
Se encontrar essas informações de autenticação, tenta baixar do registro privado especificado no arquivo de configuração um pacote com o mesmo nome, testando algumas variações, como:
Se não encontrar esses pacotes no registro privado ou não conseguir baixá-los, o código interrompe a execução. Caso consiga baixar o tarball do registro privado, faz o seguinte:
1. Executa npm install no módulo em um subdiretório chamado .documentation (a instalação é feita usando a dependência ldtzstxwzpntxqn) ou interrompe a execução.
2. Substitui o conteúdo atual do pacote pelo conteúdo do pacote recém-instalado ou interrompe a execução.
3. Exfiltra o conteúdo de dois arquivos relacionados à rede e do novo package.json por meio de uma solicitação POST:
4. Descriptografa e executa o arquivo criptografado em um novo processo independente (o algoritmo de criptografia, a chave e o IV estavam codificados; falaremos mais sobre esse arquivo na próxima seção):
5. Remove todos os arquivos relevantes (esta etapa limpa apenas os arquivos criptografados e o código de P1)
6. Encerra.
Agora que esclarecemos esses pontos, podemos concluir que, se o pacote gxm-reference-web-auth-server não estiver no registro privado da vítima, ou se ele estiver inacessível, o malware simplesmente vai ignorá-la.
Isso indica que:
O pacote tem uma organização específica como alvo.
O agente por trás desses pacotes sabe da existência desse pacote no registro privado da organização.
Com isso, concluímos todas as etapas de P1, e o “invólucro do malware” será encerrado.
Engenharia reversa de malware — P2: O agente
Como o invólucro tinha as informações codificadas para descriptografar o arquivo (um erro do adversário, acreditamos), também conseguimos descriptografá-lo — e foi o que fizemos.
Assim como o invólucro, esse arquivo também estava ofuscado e era uma única linha de caracteres sem sentido. Desta vez, porém, o desofuscador teve mais dificuldade e deixou o código cheio de enigmas:
Tentamos usar outros desofuscadores, mas nenhum produziu um resultado melhor. Mantivemos o resultado inicial e começamos a desofuscar o código manualmente. Depois de algum esforço, conseguimos transformar o arquivo em algo legível. Hora de investigar como o agente funciona.
Registro do agente
A primeira instrução dada ao agente é registrar-se no servidor de comando e controle (chamado de CNC ou C2). Com isso, o agente recebe três strings essenciais para as comunicações seguintes: uma chave e um IV para criptografar os payloads, além de um UUID.
Depois que essa solicitação é concluída, todas as comunicações subsequentes entre o servidor C2 e o agente são criptografadas e descriptografadas com essa chave e esse IV (o algoritmo estava codificado). Logo após essa solicitação, o agente envia ao servidor uma solicitação POST com informações sobre o ambiente do agente:
Depois de enviar essa solicitação, o agente passa à próxima etapa.
O loop de execução
O loop de execução do agente é bastante simples. Ele tem algumas condicionais if/else para diferentes comandos e os executa de acordo com as instruções do servidor C2. Por exemplo, o agente pode se apagar se receber o comando delete ou avaliar um trecho de código enviado pelo C2:
Em resumo, o agente responde aos seguintes comandos:
Ao analisar essa lista, observe que exec ou eval podem executar um shell reverso, dando ao invasor controle total sobre a máquina infectada. Observe também que a opção register, quando chamada novamente, pode ajudar o agente e o servidor C2 a trocar as informações de criptografia e gerar novas, caso queiram alterá-las.
Embora essa funcionalidade possa parecer única ou sofisticada, no universo dos agentes C2, ela é padrão em um agente que seria instalado na máquina da vítima. De qualquer forma, não conseguimos associar o código-fonte do agente a nenhuma estrutura C2 conhecida.
Conclusão sobre o malware
Agora que entendemos o malware por completo, podemos concluir o seguinte:
O malware tem como alvo uma única empresa, ainda desconhecida. No entanto, com base nas informações obtidas na engenharia reversa, é provável que essa empresa tenha o pacote “gxm-reference-web-auth-server” em seu registro privado.
Como o pacote procura por si mesmo no registro privado da vítima, também podemos classificá-lo como um ataque de confusão de dependências, um tipo de ataque à cadeia de suprimentos.
É provável que o(s) invasor(es) soubesse(m) da existência desse pacote no registro privado da empresa.
Embora já tivéssemos uma visão clara do funcionamento do pacote, decidimos verificar se o servidor C2 respondia e se a campanha ainda estava ativa. Hora de nos passar por outra pessoa.
Brincando de “Among Us” com um adversário
A ideia para nosso próximo experimento era simples. Queríamos descobrir se havia alguém do outro lado da linha e, em caso afirmativo, se essa pessoa estava ativa. Para isso, precisávamos:
Usar o agente sem o wrapper (o P1 filtraria nosso cliente por falta de credenciais no .npmrc, entre outros motivos).
Interceptar e registrar todo o tráfego HTTP/S (queríamos ver o que o servidor C2 enviava e recebia).
Transferir os dados registrados para nós de forma unidirecional, irreversível e impossível de rastrear (isso é importante, pois o invasor também pode acessar a máquina!).
Mas, antes de agir, queríamos coletar algumas informações sobre o próprio servidor.
Tem alguém aí?
Coletamos informações sobre o servidor usando consultas WHOIS e varreduras Nmap padrão. Os resultados do WHOIS indicaram que o servidor estava hospedado na DigitalOcean (à qual reportamos o IP do servidor), e as varreduras do Nmap mostraram o seguinte:
O servidor não era exatamente seguro e estava ativo, à escuta.
O impostor
Retomando nosso plano, precisávamos preparar o seguinte:
Código do agente: Obtivemos esse código durante a etapa de descriptografia e conseguimos executá-lo separadamente do wrapper (P1).
Interceptadores: Precisavam ser implementados no ambiente NodeJS, já que o próprio agente usava os módulos HTTP/S do NodeJS.
Pipeline de registro: Decidimos usar o Pipedream, pois ele permite um tratamento bastante granular das solicitações HTTP/S e já oferece várias integrações prontas (como armazenamento de valores, integração com Slack e muito mais).
Para a interceptação, usamos a biblioteca @gr2m/http-recorder, que fazia exatamente o que queríamos: capturar e manipular por completo as solicitações HTTP/S.
Combinando as etapas 1 e 2, chegamos ao seguinte trecho de código:
Só faltava enviar os dados para nós. Usando o Pipedream, fizemos a função send enviar o payload para nosso endpoint e processá-lo.
Para processar os dados, armazenamos o IV e a chave para descriptografar as mensagens. Também configuramos o pipeline do Pipedream para se integrar a um workspace anônimo do Slack e registrar as mensagens nele. Assim, sempre que o agente e o servidor C2 trocavam mensagens, recebíamos uma notificação no Slack com todas as informações descriptografadas e legíveis. Abaixo, uma ilustração da configuração da nossa máquina infectada:

E aqui está um exemplo do resultado no Slack:

E assim, nosso pseudo-honeypot estava pronto.
Olá, sou eu
Pouco depois de nosso agente falso entrar em atividade e começar a se comunicar com o servidor C2, os comandos começaram a chegar. O primeiro foi um ls, seguido por uma tentativa do agente de cat todos os arquivos visíveis e percorrer o sistema. Veja um exemplo de um dos comandos recebidos (após a decodificação de base64):

Nesse momento, decidimos encerrar o experimento, pois já tínhamos atingido nosso objetivo: descobrir se havia alguém do outro lado.
Isso indica que há uma campanha ativa contra os proprietários do “gxm-reference-web-auth-server” original/privado.
Mas ainda tem mais
Pouco antes de concluirmos nosso experimento, notamos algo interessante. Além do endereço C2, havia poucos valores fixos no código. Um deles era o valor da propriedade “engine” na chamada inicial /register:
Como já não nos preocupávamos em passar despercebidos, decidimos testar diferentes valores para ver se havia outros tipos de alvos. Depois de algum tempo, encontramos outros tipos de engine:
Isso indica que há pelo menos um agente para navegador e um agente em Golang, e é razoável supor que existam outros.
Divulgação e próximos passos
Embora conheçamos o funcionamento interno desse malware, não sabemos qual organização ou empresa é o alvo. Por isso, queremos usar este canal (além do Twitter e outros) para alertar a comunidade sobre esse pacote e pedir que todos ajudem a divulgar o aviso. Fique à vontade para entrar em contato conosco (ou enviar uma mensagem direta para @snyksec) se tiver dúvidas ou preocupações relacionadas a esse assunto.
Também entramos em contato com a DigitalOcean para pedir que removesse o servidor C2 de seu serviço e com o npm para informar sobre os pacotes maliciosos e solicitar que fossem removidos.
Com isso, encerramos nossa investigação. Como os ataques são complexos e sofisticados, vale destacar que o vetor inicial de infiltração deste ataque é uma simples confusão de dependências de pacotes, fácil de mitigar. Para conferir o fluxo completo deste ataque, veja a imagem abaixo. Cuide-se e mantenha seus sistemas seguros!

Proteja suas dependências de código aberto
As ferramentas da Snyk, desenvolvidas para quem programa, criam PRs de correção com um clique para dependências de código aberto vulneráveis e suas dependências transitivas.



