Pacotes maliciosos de Node.js visivelmente invisíveis: quando configurações de nicho se encontram com caracteres invisíveis
Aviad Hahami
28 de fevereiro de 2022
0 minutos de leituraNos últimos anos, vimos um enorme aumento no número de pacotes de código aberto criados e usados na prática. Hoje, cada ecossistema tem seu gerenciador de pacotes, e quase todo gerenciador tem seus recursos e configurações pouco conhecidos.
À medida que desenvolvedores instalam continuamente um número cada vez maior de pacotes, os atacantes passam a se interessar pelas superfícies de ataque desses pacotes. Começa então a jornada para criar o pacote malicioso perfeitamente oculto.
Esta publicação aborda ataques que exploram o ecossistema Node.js e os gerenciadores de pacotes yarn e npm. Mas considere-a um “chamado à ação” para investigar problemas semelhantes em outros ecossistemas, aumentar a conscientização e fortalecer a segurança geral da nossa comunidade.
Dito isso, vamos começar!
Parte 1: Configurações pouco conhecidas
Outro dia, um colega pesquisador me falou sobre algumas opções de configuração interessantes do npm, pouco conhecidas e com sérias implicações de segurança (mais tarde, encontramos opções semelhantes no yarn). Elas permitem especificar ao gerenciador de pacotes qual binário deve ser executado dentro do diretório do projeto. Isso mesmo: qual binário!
Em termos de configuração, essas opções são yarnPath no arquivo .yarnrc e git, shell e script-shell no arquivo .npmrc (entre outras). Para entender melhor o vetor de ataque a seguir, recomendo muito a leitura do artigo de Rotem (da Cider Security) sobre essa descoberta.
Alguém pode perguntar: “por que esses arquivos que acabei de clonar/baixar seriam respeitados?”. A resposta é que é assim que o npm/yarn funciona. Como parte do fluxo de gerenciamento do projeto, o gerenciador procura arquivos .rc em uma hierarquia: primeiro no diretório do projeto atual, depois no diretório do usuário e, por fim, no diretório raiz (saiba mais na documentação de configuração do NPM).
Com as opções acima, podemos criar um pacote malicioso parcialmente oculto (em repositórios/arquivos tarball, já que arquivos `.rc` não são baixados ao executar `npm install` ou equivalente), usando o seguinte arquivo .yarnrc como configuração:
(Observação: este exemplo usa um arquivo .yarnrc, mas um arquivo .npmrc semelhante — com a opção de configuração apropriada definida — terá o mesmo resultado.)
Com isso, qualquer execução de yarn install na raiz do projeto acima fará com que evil.sh seja executado. Agora, só precisamos adicionar o arquivo malicioso.
Dois pontos importantes:
Mesmo que você use
--ignore-scriptsou uma instrução equivalente, o que foi descrito acima ainda acontecerá.Se esse pacote for uma dependência do seu projeto, o arquivo .yarnrc malicioso não será respeitado, pois ele estará dentro de
node_modules, e não no mesmo nível. Portanto, nesse caso, não há risco.
Parte 2: Código escondido à vista de todos
Outro tema em alta no último ano foi o código-fonte trojanizado (ou seja, código malicioso resultante do uso de caracteres com valores Unicode diferentes de sua aparência habitual).
A ideia é bem simples: alguns caracteres, por exemplo, (que não sejam espaço, CR, LF etc.), são exibidos como espaços em branco, mas continuam sendo strings válidas.
Para aprofundar um pouco, o caractere aparentemente vazio “ ” (0x3164 em hexadecimal) é chamado de “HANGUL FILLER” e, como você pode (ou não) ver, não é um espaço e é uma “letra” válida. Podemos usar isso para ocultar uma lógica específica em trechos de código e passar despercebidos ao introduzir código malicioso.
Como as informações acima são suficientes para nossa demonstração, não vou me aprofundar no assunto. Se quiser mais informações, contexto e exemplos, recomendo muito a leitura dos materiais a seguir:
Parte 3: Binários ocultos + configurações ocultas = RCE
Com tudo isso explicado, os próximos passos do nosso ataque são simples:
Criar um pacote aparentemente inofensivo.
Criar um arquivo malicioso (com um nome que use um caractere oculto).
Criar um arquivo .rc.
Definir o parâmetro de configuração para apontar para o nome do binário com o caractere invisível.
Depois de concluir essas etapas, estamos prontos. Vamos pensar nesse ataque do ponto de vista das vítimas:
Se um desenvolvedor encontrar um pacote desse tipo sem conhecer o vetor de ataque, provavelmente não perceberá os sinais de alerta.
Talvez tenha um pouco de cautela e execute ls -l. Nesse caso, verá:
Como a saída não parece inesperada, essa é a primeira armadilha. Nesse momento, a maioria dos alvos executará yarn install e se tornará vítima.
Mas vamos supor que nosso alvo seja um desenvolvedor experiente e adicione a opção “mostrar arquivos invisíveis” ao ls. O resultado será:
Agora, o alvo pode pensar: “certo, o que tem no arquivo .rc?” (ignorando completamente o arquivo discreto e invisível) e executar cat. O resultado será:
Essa é a segunda armadilha.
A maioria dos desenvolvedores não sabe que o yarn (ou o npm) pode executar binários personalizados. Ver algo como o exemplo acima é estranho, sim, mas não diz “evil.sh”, certo?
A terceira armadilha é bem simples: o alvo simplesmente não vê o arquivo. Mesmo que tenha visto o arquivo .rc e consultado a especificação de yarn-path (o npm tem algo equivalente), pode presumir que a propriedade não aponta para nada e acabar executando o gerenciador de pacotes mesmo assim.
Na minha PoC, executar yarn install resulta em:
Como se proteger e considerações
Você pode se proteger seguindo estas recomendações:
Nunca confie em código (de terceiros). Confira sempre o que você clonou/baixou e fique atento a comportamentos e configurações suspeitos.
Execute em um ambiente isolado. Sempre execute código de terceiros em um ambiente isolado, como um sandbox ou contêiner. Assim, mesmo que o pacote seja malicioso, provavelmente não afetará seu sistema nem comprometerá sua máquina.
Observação: executar em um sandbox talvez não seja 100% seguro, já que existem vulnerabilidades no kernel, escapes de sandbox etc. Ainda assim, esse tipo de ambiente reduz bastante o risco e bloqueia a maioria dos ataques simples (como um shell reverso básico ou equivalente).
E pronto: mitigamos o ataque.
O fascinante desse vetor de ataque é que ele é completamente visível e, ao mesmo tempo, invisível.
Alguém pode dizer: “Eu sempre executo ls -la, verifico todos os arquivos e conheço todas as opções de configuração do gerenciador de pacotes.” Tudo bem, mas será mesmo? Ampliando essa ideia: isso também vale para todas as pessoas da sua organização?
E, assim como nos filtros de Bloom, a resposta aqui é: “não significa não; sim significa talvez”.
Para encerrar, quero reforçar que este é um chamado à ação (e não à exploração) para que a comunidade aumente a conscientização sobre vetores de ataque como esse. Com tantas pessoas criando aplicações e instalando dependências quase às cegas, é essencial alertar outras pessoas sobre configurações suspeitas.
Comece a resolver desafios de capture the flag
Aprenda a resolver desafios de capture the flag assistindo sob demanda ao nosso workshop virtual introdutório.



