Skip to main content

Análise pós-incidente do backdoor malicioso no event-stream

neweracracker suspicious issue

6 de dezembro de 2018

0 minutos de leitura

Na semana passada, aconteceu o impensável. Um pacote malicioso, flatmap-stream, foi publicado no npm e, mais tarde, adicionado como dependência ao pacote event-stream, amplamente utilizado, pelo usuário right9ctrl. Algum tempo e 8 milhões de downloads depois, aplicativos em toda a web executavam, sem saber, código malicioso em produção. Na semana passada, pouco depois de o incidente vir à tona, publicamos nossas primeiras impressões no blog. Agora, podemos fazer uma análise pós-incidente mais aprofundada, incluindo uma linha do tempo dos acontecimentos. Agradecemos a todas as pessoas que também investigaram o caso, em especial ao usuário do GitHub maths22, que fez engenharia reversa do código malicioso.

O que é o pacote event-stream?

O pacote event-stream é um conjunto de ferramentas com utilitários para criar e gerenciar streams. Criado por Dominic Tarr (~dominictarr no npmjs), é um dos 422 pacotes que ele mantém no npmjs. O pacote event-stream teve um total de 84 versões, começando pela v0.5.2, em 2011, e com lançamentos regulares até a versão 3.3.4, há dois anos.

Ao longo de todo o desenvolvimento do event-stream, o projeto recebeu contribuições de 33 colaboradores diferentes, mas a maioria delas aconteceu nos primeiros anos. Desde então, apenas mudanças pontuais foram revisadas:

Gráfico que mostra as adições semanais ao GitHub em verde e as exclusões em vermelho, com a maior atividade concentrada por volta de 12 e 13 de janeiro.

O projeto recebeu mais de 2.000 estrelas, foi bifurcado 139 vezes e 62 usuários do GitHub se inscreveram para receber notificações sobre mudanças no projeto. Outros 3.931 pacotes usavam o projeto (sem contar os pacotes com escopo).

Linha do tempo dos acontecimentos

A linha do tempo a seguir mostra alguns dos principais marcos na história do projeto e os momentos-chave do incidente malicioso. Vamos analisar cada ponto da linha do tempo, entre outros aspectos, em detalhes abaixo.

Linha do tempo do event-stream, da Snyk, mostrando a criação do pacote em 2011, sua manutenção, mudanças nas versões e relatos do incidente de segurança de novembro de 2018

Sequência de acontecimentos

Vamos analisar a sequência de acontecimentos que levou ao uso do pacote malicioso flatmap-stream. As informações foram pesquisadas em fontes públicas do GitHub, no cache do Google e no npm.

31 de julho de 2015: o usuário do GitHub devinus comenta em uma issue do projeto event-stream e pergunta se a funcionalidade flatmap seria bem-vinda. O mantenedor do pacote, dominictarr, responde positivamente e afirma que aceitaria uma contribuição de um usuário:

Discussão no GitHub sobre aceitar uma alteração no flatMap e por que mapSync usa emit em vez de queue, com um link para pull-stream.

Podemos especular que o usuário malicioso right9ctrl, descoberto posteriormente, pode ter usado essa informação para planejar e executar um elaborado ataque de engenharia social contra o projeto.

5 de agosto de 2018: um usuário identificado como "Antonio Macias" no npm criou e publicou um pacote não malicioso chamado flatmap-stream.

Em seguida, Antonio Macias propôs que o projeto event-stream usasse o pacote flatmap. O usuário do GitHub right9ctrl procurou Dominic Tarr, oferecendo ajuda no projeto e propondo fazer as alterações necessárias para adicionar a funcionalidade flatmap, incluindo a dependência flatmap-stream. Dominic aceitou a oferta de right9ctrl e o tornou colaborador do projeto event-stream no GitHub, além de conceder a right9ctrl direitos completos de publicação do módulo no ecossistema npm. Mais tarde, durante o relatório do incidente, Dominic confirmou que já não tinha direitos de publicação do módulo no npm e, por isso, não podia corrigir o problema removendo do npm a versão infectada 3.3.6.

Pouco depois, right9ctrl enviou uma série de commits aparentemente inofensivos ao repositório do event-stream no GitHub:

4 de setembro de 2018: uma atualização de dependência aparentemente inofensiva.

4 e 5 de setembro de 2018: exemplos de map e split adicionados: 0cc, ee8, c08, 05b

5 de setembro de 2018: lançamento da versão 3.3.5 do event-stream com 918.

9 de setembro de 2018: a dependência flatmap-stream foi adicionada em e31

9 de setembro de 2018: uma nova versão minor do event-stream, 3.3.6, foi criada em 599

16 de setembro de 2018: flatmap-stream foi removido do código do event-stream em 908 e da árvore de dependências em 2bd. Em seguida, foi lançada a versão major 4.0.0.

20 de setembro de 2018: right9ctrl faz outras mudanças cosméticas no código que aprimoram as palavras-chave do projeto em 60d, provavelmente para melhorar ainda mais os resultados de busca no site oficial do registro npmjs.com.

5 de outubro de 2018: uma nova versão minor, flatmap-stream@0.1.1, foi lançada com o ataque de injeção no código-fonte minificado. A partir de então, as instalações do event-stream também passaram a baixar a nova versão infectada 0.1.1 do flatmap como dependência transitiva.

Não há mais indícios de atividades do usuário right9ctrl no projeto event-stream. O perfil foi removido do GitHub e do npm, embora ainda possa ser acessado pelo cache do Google para fins de investigação:

Perfil do GitHub de right9ctrl, com três repositórios populares e um mapa de contribuições que mostra 22 contribuições no último ano.

29 de outubro de 2018: jaydenseric abriu uma issue no nodemon relatando um aviso inesperado de descontinuação. A mensagem está alinhada à recomendação do OpenSSL de usar um algoritmo mais moderno no lugar de EVP_BytesToKey. A recomendação é que os desenvolvedores derivem a chave e o IV por conta própria usando crypto.scrypt() e crypto.createDecipheriv() para criar o objeto Decipher.

Issue do GitHub com o título “Aviso de descontinuação ao iniciar #1442”, que descreve um aviso do Nodemon ao iniciar o Node.js e mostra detalhes da versão e um trecho de comando.

19 de novembro de 2018: NewEraCracker abriu uma issue no event-stream.

Captura de tela de um comentário que aponta atualizações suspeitas do npm para as versões 0.1.0, 0.1.1 e 0.1.2 do flatmap-stream.

19 de novembro de 2018: NewEraCracker abriu uma issue no nodemon.

Problema do GitHub intitulado “Subdependência com backdoor? flatmap-stream-0.1.1 e flatmap-stream-0.1.2”, que detalha uma cadeia de dependências suspeita.

20 de novembro de 2018: FallingSnow suspeita de um ataque de injeção.

20 de novembro de 2018: FallingSnow abre a issue no event-stream.

26 de novembro de 2018: uma publicação no HackerNews é feita, e o caso repercute nas redes sociais.

26 de novembro de 2018: o pacote flatmap-stream foi removido do npm.

26 de novembro de 2018: vários usuários reportam o problema à Snyk pelo formulário de envio de vulnerabilidades.

26 de novembro de 2018: a Snyk adiciona o pacote malicioso à sua base de dados de vulnerabilidades e notifica todos os projetos monitorados.

26 de novembro de 2018: Danny Grander, da Snyk, reporta o problema ao grupo de trabalho de segurança da Node.js Foundation.

27 de novembro de 2018: a Snyk publica um artigo no blog sobre o problema.

O alvo: Copay

Uma análise mais detalhada do código do flatmap-stream revela que esse foi um ataque direcionado com precisão ao Copay, uma plataforma segura de carteira de bitcoin.

O código malicioso do flatmap-stream foi baixado milhões de vezes e executado muitas outras milhões de vezes. Os atacantes poderiam ter feito inúmeras coisas nefastas, mas, em vez disso, esperaram a oportunidade de executar o código durante a compilação do aplicativo Copay. E conseguiram: o código foi incorporado às versões 5.0.2 a 5.1.0 do Copay.

O código de descriptografia procurava a chave em uma variável de ambiente chamada npm_package_description. O npm define essa variável com a descrição do pacote raiz. A descriptografia só ocorreria se o aplicativo cliente fosse a carteira de bitcoin Copay, que usava a chave para descriptografar a carga útil com o texto “A Secure Bitcoin Wallet”. maths22 descobriu essa informação ao testar várias descrições de pacotes npm por força bruta.

Para descobrir isso, o usuário maths22 testou várias descrições de pacotes npm como chaves para descriptografar a carga útil. Mas não parava por aí: uma segunda carga útil era executada ao executar comandos de build específicos, ou seja, somente durante a compilação dos aplicativos para iOS, Android ou desktop.

A terceira e última carga útil é um código JavaScript injetado em outra dependência, especificamente em ./node_modules/@zxing/library/esm5/core/common/reedsolomon/ReedSolomonDecoder.js. Diferentemente das duas primeiras cargas úteis, executadas durante a compilação, essa era executada dentro do próprio aplicativo.

O código malicioso roubava bitcoins e as chaves privadas da carteira caso o saldo ultrapassasse 100 bitcoins ou 1.000 BHC (Bitcoin Cash). A Copay enviou aos usuários as seguintes recomendações:

Os usuários não devem transferir fundos para novas carteiras importando as frases de recuperação de 12 palavras das carteiras afetadas, pois as chaves privadas correspondentes podem ter sido comprometidas. Primeiro, atualize as carteiras afetadas (5.0.2 a 5.1.0). Em seguida, transfira todos os fundos para uma carteira nova com a versão 5.2.0, usando o recurso Enviar máximo para realizar transações com o valor total.

A recomendação também era que os usuários “presumissem” que suas chaves privadas poderiam ter sido comprometidas e transferissem “imediatamente” todos os fundos para novas carteiras seguras na versão 5.2.0.

A análise dos acontecimentos e do ataque mostra que foi uma ação bem planejada e executada por profissionais, e que provavelmente levou meses de preparação.

Conclusão

A sequência de acontecimentos descrita neste artigo é mais um lembrete de como o modelo de código aberto pode ser frágil quando não é valorizado. Se apenas uma pequena parcela de quem usa pacotes amplamente adotados, como o event-stream, contribuísse com eles e retribuísse o valor que recebem, a tomada maliciosa do projeto poderia ter sido facilmente evitada. O pacote event-stream era usado como dependência em todo o ecossistema npm, estando presente em pelo menos 3.931 pacotes. Entre os mais conhecidos, estão @vue/cli-ui, vscode, nodemon e ps-tree.

O pacote malicioso poderia ter passado despercebido se a mensagem de descontinuação não tivesse levado Jayden Seric a abrir uma issue no pacote nodemon. Sem isso, provavelmente só teria sido descoberto muito tempo depois.

Na Snyk, defendemos a divulgação responsável e incorporamos a pesquisa de segurança à nossa cultura. Se você descobrir uma vulnerabilidade que queira divulgar de forma responsável, fale com a gente pelo nosso formulário de divulgação responsável.

Enviar uma divulgação.

Comece a jogar Capture the Flag

Aprenda a resolver desafios de Capture the Flag assistindo sob demanda ao nosso workshop virtual introdutório.