TL; DR
Un gruppo di cinque pacchetti npm, pubblicato su due account handle, spedito un postinstall Un hook che legge le credenziali cloud dall'host e le invia fuori dal dispositivo. I pacchetti hanno nomi fantasma e pirata — coral-wraith, ecto-corsair-whisper-6f3b9, ecto-corsair-flag-x9m4, ecto-rust-read-f3a9c1, ecto-nightly-spirit — e serializzare tutto ciò che raccolgono in un falso ecto_module: Manifesto YAML prima di trasmetterlo. Tracciamo il cluster come ectoplasma.
Il payload viene eseguito solo quando rileva un ambiente specifico: un host il cui nome è un stringa esadecimale di 12 caratteri e una directory di lavoro sotto /app/node_modules — la forma di un worker di build o CI containerizzato. Quando quel passaggio viene effettuato, l'hook interroga il Servizio di metadati delle istanze AWS (IMDSv2) per le credenziali del ruolo IAM, elenca AWS Secrets Manager in tre regioni, scarica le variabili d'ambiente, legge i file sotto /appe raccoglie stringhe di cattura della bandiera. Quindi esfiltra il risultato in due modi: un faro verso un webhook.site un raccoglitore e un manifesto PUT verso un endpoint IP non elaborato, con un elenco di fallback che privilegiano localhost.
Le descrizioni dei pacchetti più recenti recitano "Payload CTF per il test della catena di fornitura Verdaccio". Riportiamo questa autodescrizione come un fatto osservabile. Il comportamento in sé – traffico in uscita reale verso un IP pubblico, letture reali delle credenziali IMDS, chiamate reali a Secrets Manager – è quello che è, indipendentemente dall'etichetta, ed è il motivo per cui queste versioni sono state classificate come dannose.
Un nome nel gruppo, coral-wraith, non si è fermato a una singola pubblicazione. È stato ripubblicato in rapida successione attraverso decine di versioni nel giro di poche ore — 1.0.0 salendo a 6.0.0 — e una precedente edizione con lo stesso nome aveva utilizzato gonfiato 9999.0.x numeri di versione, la forma classica di un tentativo di confusione della dipendenzaNel corso di questo processo, il payload è visibilmente maturato: da un semplice segnale di enumerazione degli host a un sistema completo di gestione delle credenziali AWS, integrato da controlli ambientali che ne garantiscono la silenziosità al di fuori del target previsto.
| personalizzati | 5 nomi; coral-wraith da solo ripubblicato in decine di versioni |
| Ecosistema | npm |
| Installa vettore | postinstall script del ciclo di vita |
| Obiettivo primario | Credenziali del ruolo AWS IAM + valori segreti di Secrets Manager, variabili d'ambiente, /app file |
| Estrazione | webhook.site beacon + raw-IP C2 PUT |
| Cancello di innesco | Nome host in formato esadecimale a 12 cifre + /app/node_modules cwd, più un controllo ambientale che sopprime il payload al di fuori di tale contesto |
| Gravità | alto — divulgazione di credenziali cloud e segreti gestiti da ambienti di compilazione e runtime containerizzati |
Attacco anatomico
Ogni pacchetto nel cluster è costruito allo stesso modo: un quasi vuoto index.js (module.exports = {}), una riga pacchetto.json sceneggiatura — “postinstall”: “node postinstall.js” — e il carico utile in postinstall.jsÈ sufficiente installare il pacchetto per eseguire l'hook; non sono necessarie importazioni o chiamate.
Il cancello di puntamento. Prima di fare qualsiasi cosa, il carico utile della famiglia ecto controlla l'ambiente circostante:
function isAppWorker(): host = os.hostname() if host does NOT match /^[0-9a-f]{12}$/ -> exit if cwd does NOT contain "/app/node_modules" -> exit if cwd contains "/tmp/npm-safe" -> exit otherwise -> proceed Un nome host esadecimale a 12 cifre è la forma predefinita che Docker assegna a un container e /app/node_modules è un percorso di installazione convenzionale all'interno di un container. La terza clausola si interrompe se il percorso assomiglia a una directory di estrazione in una sandbox. L'effetto netto è che il payload rimane inattivo su un laptop di uno sviluppatore o in una sandbox di analisi e si attiva solo all'interno di un worker di build o runtime containerizzato, ovvero il tipo di ambiente che più probabilmente contiene credenziali cloud attive. Il primo pacchetto nel cluster, spettro corallino, non ha tale porta e esegue la sua collezione (più semplice) incondizionatamente.
Collezione. Quando il cancello passa, il gancio fuoriesce attraverso execFileSync(“/bin/sh”, [“-c”, …]) e esegue un singolo comando composto che, in ordine:
1. PUT /latest/api/token to 169.254.169.254 (IMDSv2 token request) 2. GET .../iam/security-credentials/ (IAM role name) 3. GET .../iam/security-credentials/<role> (temporary credentials) 4. dump env | sort (environment variables) 5. list /app (excl. node_modules) + cat first 15 (application files) 6. aws secretsmanager list-secrets (us-east-1, eu-west-1, eu-central-1) 7. scrape readable files for HTB{...} (capture-the-flag strings) I passaggi 1–3 sono un recupero IMDSv2 da manuale: richiedere un token di sessione, quindi allegarlo come X-aws-ec2-metada-token intestazione per estrarre il ruolo IAM dell'istanza e le chiavi di accesso temporanee di tale ruolo. La scelta di implementare IMDSv2 piuttosto che il più semplice IMDSv1 non autenticato GET Vale la pena notare che significa che il payload funziona anche su istanze configurate per richiedere l'accesso ai metadati basato su token, che è la misura di sicurezza raccomandata da AWS. Le credenziali restituite dal passaggio 3 hanno una durata limitata. ID Chiave di Accesso/Chiave di accesso segreta/Token Triple con ambito limitato al ruolo dell'istanza; qualsiasi cosa quel ruolo possa fare, il detentore di quelle chiavi può farla per tutta la durata della credenziale.
I passaggi 4–6 allargano l'azione. ENV Il dump cattura tutto ciò che il processo di compilazione o di runtime ha ereditato: in pratica, è qui che risiedono più spesso i token di registro, le stringhe di connessione al database e le chiavi API. / app Il percorso dei file legge fino a quindici file di applicazione esterni nodi_moduli, che può essere configurato in superficie, .env file o sorgente. Il passaggio 6 chiama Elenco dei segreti di AWS Secret Manager in tre regioni; le credenziali recuperate nei passaggi 1-3 sono esattamente ciò che autentica tali chiamate, quindi la lettura IMDS e la catena di enumerazione di Secrets Manager si uniscono in un'unica escalation: ruolo dell'istanza → inventario dei segreti gestiti. Il passaggio 7 è un riferimento alla struttura del capture-the-flag: quando un HTB{…} Se viene trovata la bandiera, viene inviata da sola; altrimenti, il blocco di dati grezzo raccolto viene suddiviso in quattro parti e inviato.
Le versioni successive nel cluster portano avanti l'escalation. Invece di fermarsi a un inventario, analizzano la risposta IMDS, esportano le chiavi temporanee come AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / TOKEN DI SESSIONE AWS variabili d'ambiente, confermano l'identità con aws sts get-caller-identitye quindi itera su ogni segreto restituito da segreti della lista chiamata aws secretsmanager get-secret-value su ciascuno — recuperando il contenuto segreto, non solo i loro nomi. Le stesse versioni leggono anche i binari flag sostituiti dal processo (/segnale di lettura e amici) e tentare un corsa di carica contro qualsiasi progetto Rust trovato sotto / app, ampliando la raccolta dati oltre le credenziali cloud, fino a includere tutto ciò che l'ambiente di compilazione espone.
Queste versioni successive si limitano anche in modo più aggressivo. Oltre al nome host esadecimale a 12 cifre e /app/node_modules Il payload esegue dei controlli, ispeziona la configurazione del registro dei pacchetti attivo e il percorso della directory di lavoro ed esce silenziosamente quando questi indicano un contesto di analisi o di mirroring anziché un target attivo. L'effetto combinato è un payload che non fa nulla di osservabile nella maggior parte degli ambienti di ispezione ed esegue la sua raccolta completa solo quando ritiene di trovarsi su un host containerizzato autentico.
exfiltrationI dati raccolti lasciano l'host su due canali. In primo luogo, un beacon POST a un fisso webhook.site raccoglitore, contenente il nome host, l'UID numerico, la directory di lavoro e fino a 120 KB di dati raccolti. In secondo luogo, i dati vengono ripiegati in un falso "manifesto del modulo" YAML e PUT a /api/moduli/ sul server di destinazione:
ecto_module: name: "<flag-or-chunk-0>" version: "1.0.0" power_level: "<chunk-1>" ship_deck: "<chunk-2>" cargo_hold: "<chunk-3>" I nomi dei campi manifesti (livello_di_potenza, ponte della nave, stiva) sono decorazioni: i dati rubati si trovano all'interno dei valori stringa, motivo per cui un monitor di rete vede quello che sembra un caricamento benigno del manifesto del registro dei pacchetti piuttosto che un ovvio dump di dati. Il canale beacon trasporta di più: il POST corpo a webhook.site Include il nome host, l'UID numerico, la directory di lavoro e fino a 120 KB del blob raccolto, quindi anche un singolo beacon riuscito fornisce l'intero set di dati. webhook.site è un servizio gratuito di ispezione delle richieste; utilizzandolo come sistema di raccolta, l'operatore non deve mai predisporre una propria infrastruttura di ricezione per quel canale e le richieste registrate rimangono memorizzate nel cestino del servizio.
Il manifesto PUT percorre un elenco di fallback che inizia con diversi 127.0.0.1/localhost porti e poi passa attraverso tre indirizzi pubblici nel154.57.164.0/24' L'intervallo si ferma al primo endpoint che risponde con uno stato 2xx. L'ordine localhost-first è coerente con l'autodescrizione "verdaccio testing" (un registro locale su loopback), ma i fallback IP pubblici implicano che i dati lascino l'host ogni volta che loopback non è in ascolto, ovvero su qualsiasi macchina che non sia il banco di prova dell'autore.
timeline
Il cluster mostra una crescita incrementale delle capacità piuttosto che un calo repentino. Lo ordiniamo in base al comportamento osservato, non in base al tempo di pubblicazione effettivo:
| Stage | Pacchetti / Versioni | Comportamento |
|---|---|---|
| Corsa mattutina | coral-wraith 9999.0.x | Numeri di versione gonfiati coerenti con un tentativo di confusione delle dipendenze; enumerazione e esfiltrazione in fase di installazione |
| Seme | coral-wraith 1.0.0 | postinstall raccoglie i file id/env/flag; singolo PUT a 154[.]57[.]164[.]71:30782, marcatore ECT-472839 |
| Iterazione rapida | coral-wraith 1.0.1 → 6.0.0 | Decine di rilasci in poche ore; il carico utile acquisisce il isAppWorker() cancello, recupero credenziali IMDSv2, il completo get-secret-value pivot, controllo dell'ambiente del registro/percorso e marcatori di doppio sink |
| Nomi paralleli | ecto-corsair-whisper-6f3b9 1.0.14–1.0.18 | Stesso carico utile controllato; webhook.site Elenco di fallback per beacon e endpoint multipli |
| Varianti | ecto-rust-read-f3a9c1 1.0.1–1.0.2 | Aggiunge indicatori di lavandino extra ECT-987654, ECT-654321, ECT-839201 |
| Varianti | ecto-corsair-flag-x9m4 1.0.0, ecto-nightly-spirit 1.1.0 | Stesso carico utile controllato, stesso C2 e stesso faro |
La caratteristica distintiva del cluster è la sua cadenza di pubblicazione: anziché un pacchetto e una versione, lo stesso nome viene ripubblicato più e più volte in rapida successione, ogni rilascio una piccola variazione della precedente, insieme a una manciata di fratelli con nomi diversi che trasportano lo stesso payload. All'interno del sussurro famiglia il codice diviso in due impronte digitali vicine — un set che attiva due rilevamenti critici, un altro tre (un ulteriore sink di lettura file) — ma entrambi si risolvono nello stesso payload; la differenza è la deriva del codice, non una biforcazione comportamentale. Versioni di sussurro oltre l'intervallo analizzato (fino ad almeno 1.0.25 al momento della stesura) sono stati osservati in tempo reale sul registro e il spettro corallino il nome ha continuato a scalare la propria scala di versioni nella stessa finestra.
Indicatori di compromesso
Tutti gli indicatori riportati di seguito sono stati estratti dal codice sorgente del pacchetto su disco. Gli indicatori di rete sono stati depotenziati.
Reti
| Ruolo | |
|---|---|
hxxp://154[.]57[.]164[.]71:30782 | Obiettivo C2 PUT (coral-wraith) |
hxxp://154[.]57[.]164[.]80:30543 | C2 PUT fallback (ecto-*) |
hxxp://154[.]57[.]164[.]82:31250 | C2 PUT fallback (ecto-*) |
hxxp://154[.]57[.]164[.]71:31289 | C2 PUT fallback (ecto-*) |
hxxps://webhook[.]site/602a4c72-7033-4e28-92ea-dc66e59206e5 | Collezionista di fari |
169[.]254[.]169[.]254/latest/... | Lettura delle credenziali IMDSv2 (lato destinazione, metadati AWS) |
Comportamentale/file
| Ruolo | |
|---|---|
"postinstall": "node postinstall.js" | Installa vettore |
ecto_module: YAML con power_level / ship_deck / cargo_hold Tasti | Schema del manifesto Exfil |
Indicatori di lavandino ECT-472839, ECT-987654, ECT-654321, ECT-839201 | Segmento del percorso C2 /api/modules/<marker> |
isAppWorker() cancello: host /^[0-9a-f]{12}$/, cwd contiene /app/node_modules | Condizione di attivazione |
aws secretsmanager list-secrets ancora us-east-1, eu-west-1, eu-central-1 | Enumerazione dei segreti |
HTB{...} scraping regex | Raccolta di cattura della bandiera |
Hash dei file (sha256, acquisiti al momento dell'analisi)
| Compila il | sha256 |
|---|---|
coral-wraith/postinstall.js | ce5ff035cfdfed1d0015446424b352c27b66bcb77e9fdb0a51e4245199146824 |
ecto-corsair-whisper-6f3b9 1.0.18/postinstall.js | b58432acba376aa6976f0490d9a1c04257ccdbc856d8390260c50322d63e31c3 |
Attribuzione e comportamento osservato
I cinque nomi dei pacchetti sono stati pubblicati sotto due handle di account npm, ma condividono un'infrastruttura sufficiente per trattarli come un unico cluster: lo stesso modulo ecto schema manifesto, lo stesso ECT-472839 marcatore di affondamento primario, lo stesso webhook.site ID del collettore e endpoint C2 nello stesso Blocco 154.57.164.0/24`. Il pacchetto seed (`coral-wraith, più semplice e senza gate) e la famiglia ecto con gate si presentano quindi come iterazioni su un unico set di strumenti piuttosto che come sforzi indipendenti.
I pacchetti si descrivono, nelle versioni successive, come "Carico utile CTF per il test della catena di fornitura Verdaccio." Presentiamo tale etichetta come un fatto osservabile e non la riformuliamo come una scoperta sullo scopo. Ciò che il codice fa è inequivocabile e indipendente da come viene etichettato: legge le credenziali del ruolo IAM dal servizio di metadati dell'istanza, enumera i segreti gestiti in tre regioni AWS e trasmette i risultati a un IP pubblico e a un raccoglitore di webhook di terze parti. Un ambiente di test che utilizzi esclusivamente il loopback non avrebbe bisogno dell'elenco di fallback IP pubblico, della lettura delle credenziali IMDS o delle chiamate a Secrets Manager tra regioni. Poiché l'uscita e l'accesso alle credenziali sono reali, le versioni con accesso limitato sono state classificate come dannose.
La caratteristica operativa più rilevante è la limitazione all'utilizzo esclusivo dei container. Si tratta sia di una misura di elusione, che non rileva alcun problema su laptop e ambienti di test, sia di una misura mirata, che si attiva solo laddove è più probabile la presenza di un ruolo IAM reale e di segreti attivi. Gli analisti che eseguono questi pacchetti in un ambiente di test generico non noterebbero nulla; il comportamento si manifesta solo con un nome host in stile Docker e un percorso di installazione all'interno di un container.
Impatto, tendenze e linee guida per i difensori
Il problema in questo caso riguarda la divulgazione di credenziali cloud e segreti all'interno dei container di build e runtime. Una credenziale di ruolo IAM estratta da IMDS contiene tutte le autorizzazioni associate a quel ruolo; gestore dei segreti: Elenco dei segreti (e qualsiasi seguito) OttieniValoreSegreto) estende tale concetto ai segreti delle applicazioni archiviati. I dump delle variabili d'ambiente spesso contengono token di registro, URL di database e chiavi API. In un contesto CI o container, esattamente ciò che il gate seleziona, una singola installazione transitiva di uno di questi pacchetti è sufficiente a far trapelare tali informazioni.
Ectoplasm si inserisce in uno schema che continuiamo a osservare: payload installati che accedono ai metadati cloud e ai segreti gestiti anziché ai file locali, e che si attivano solo in ambienti ad alto valore. Seguono due osservazioni difensive.
- La forma è rilevabile. Un hook di installazione npm/PyPI il cui grafico delle chiamate raggiunge sia un'API di segreti cloud (gestore di segreti AWS, segreti di Google Cloud, az keyvault) o l'indirizzo IMDS e un sink di uscita di rete è uno schema ristretto e ad alto segnale: non si verifica quasi mai in uno script del ciclo di vita legittimo. analisi del flusso statico può segnalarlo senza dipendere da alcun dominio o indirizzo IP specifico.
- L'indurimento ambientale ne attenua l'effetto. L'applicazione di un limite di hop pari a 1 per IMDSv2 impedisce ai carichi di lavoro dei container di raggiungere i metadati dell'istanza; la limitazione dei ruoli IAM al minimo privilegio limita il raggio d'azione di qualsiasi credenziale che venga divulgata; e l'esecuzione delle installazioni con –Ignore-scripts In CI, il vettore install-hook viene rimosso completamente per i pacchetti che non ne hanno bisogno.
Per i difensori, i controlli pratici sono: avviso sulle connessioni in uscita dai container di build/CI verso IP pubblici non consentiti durante installazione di npm; prestare attenzione agli accessi IMDS provenienti da script del ciclo di vita dei pacchetti; e considerare sospetto qualsiasi hook di installazione che si connetta a una CLI cloud fino a prova contraria.
Due ulteriori note specifiche per questo cluster. In primo luogo, poiché l'attivazione è limitata agli ambienti containerizzati, un pacchetto che appare inerte quando viene verificato su una workstation può comunque essere attivo in produzione: la verifica deve riprodurre le condizioni di hostname e percorso del container, oppure leggere direttamente il codice sorgente, anziché basarsi su "L'ho installato e non è successo nulla". In secondo luogo, l'utilizzo di un servizio pubblico di ispezione delle richieste come raccoglitore di beacon significa che alcuni dei dati esfiltrati potrebbero essere recuperabili per la risposta agli incidenti: un'organizzazione che trova uno di questi pacchetti nel proprio albero delle dipendenze può dedurre cosa avrebbe contenuto un beacon riuscito dalla logica di raccolta del payload e dovrebbe ruotare tutte le credenziali dei ruoli IAM, i token di registro e i segreti gestiti che erano accessibili dall'ambiente di build o di runtime interessato. Rotazione delle credenzialiLa soluzione operativa, una volta eseguita l'installazione, non la rimozione del pacchetto, è la correzione.




