TL; DR
Un gruppu di cinque pacchetti npm, publicatu in dui handle di contu, speditu un postinstall hook chì leghje l'infurmazioni d'accessu à u cloud da l'ospite è li spedisce fora di a scatula. I pacchetti portanu nomi di fantasma è pirata — coral-wraith, ecto-corsair-whisper-6f3b9, ecto-corsair-flag-x9m4, ecto-rust-read-f3a9c1, ecto-nightly-spirit - è serializà tuttu ciò ch'elli raccolgenu in un falsu ecto_module: Manifestu YAML prima di trasmettelu. Tracciamu u cluster cum'è Ectoplasma.
U payload funziona solu quandu detecta un ambiente specificu: un host chì u so nome hè un Stringa esadecimale di 12 caratteri è un cartulare di travagliu sottu /app/node_modules — a forma di una custruzzione cuntainerizzata o di un travagliadore CI. Quandu quella porta passa, u ganciu interroga u Serviziu di metadati di l'istanza AWS (IMDSv2) per e credenziali di u rolu IAM, enumera AWS Secrets Manager in trè regioni, scarica e variabili d'ambiente, legge i fugliali sottu /app, è raschia per e stringhe di cattura di a bandiera. Dopu esfiltra u risultatu in dui modi: una balisa à un webhook.site cullettore è un manifestu PUT à un endpoint IP crudu, cù una lista di fallbacks localhost-first.
E descrizzioni successive di i pacchetti dicenu "Carica utile CTF per i test di a catena di furnimentu di verdaccio". Segnalemu sta autodescrizzione cum'è un fattu osservabile. U cumpurtamentu stessu - uscita in diretta versu un IP publicu, letture di credenziali IMDS reali, chjamate di Secrets Manager reali - hè ciò chì hè indipendentemente da l'etichetta, è hè a ragione per a quale queste versioni sò state classificate maliziose.
Un nome in u gruppu, coral-wraith, ùn s'hè firmatu à una sola versione. Hà ripublicatu in rapida successione attraversu decine di versioni in poche ore — 1.0.0 arrampicata à 6.0.0 — è una corsa precedente di u listessu nome avia utilizatu gonfiatu 9999.0.x numeri di versione, a forma classica di un tentativu di cunfusione di dipendenzaÀ traversu quellu churn, u payload hè maturatu visibilmente: da una balisa di enumerazione di l'ospite à un solu colpu à un pivot di credenziali AWS cumpletu avvoltu in cuntrolli d'ambiente chì u mantenenu silenziu fora di u so target previstu.
| Packages | 5 nomi; coral-wraith solu ripublicatu in decine di versioni |
| Ecosistema | npm |
| Installà u vettore | postinstall script di u ciclu di vita |
| Obiettivu primariu | Credenziali di rolu AWS IAM + valori secreti di Secrets Manager, variabili d'ambiente, /app schedari |
| Esfilà | webhook.site beacon + IP crudu C2 PUT |
| Porta di scattu | Nome di l'ospite 12-hex + /app/node_modules cwd, più una verificazione di l'ambiente chì sopprime u payload fora di quellu cuntestu |
| Severità | altu — divulgazione di credenziali in nuvola è secreti gestiti da ambienti di compilazione è di runtime containerizzati |
Anatomia di l'attaccu
Ogni pacchettu in u cluster hè custruitu in u listessu modu: un pacchettu guasi viotu index.js (modulu.esporta = {}), una sola linea package.json script — "postinstallazione": "nodu postinstallazione.js" — è a carica utile in postinstallazione.jsL'installazione di u pacchettu hè sufficiente per eseguisce u hook; ùn hè necessaria alcuna importazione o chjama.
A porta di mira. Prima di fà qualcosa, u payload di l'ecto-famiglia verifica i so dintorni:
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 d'ospite di 12 esadecimali hè a forma predefinita chì Docker attribuisce à un container, è /app/node_modules hè un percorsu d'installazione cunvinziunale in container. A terza clausula si salva se u percorsu s'assumiglia à un cartulare d'estrazione sandboxed. L'effettu netu hè chì u payload ferma dormente in un laptop di sviluppatore o in una sandbox d'analisi è si attiva solu in un worker di compilazione o runtime containerizatu - u tipu d'ambiente più prubabile di purtà credenziali cloud in diretta. U pacchettu più anticu in u cluster, corallo-spettru, ùn hà micca una tale porta è esegue a so cullezzione (più simplice) senza cundizioni.
CullezioneQuandu a porta passa, u ganciu si sparghje. execFileSync("/bin/sh", ["-c", ...]) è esegue un unicu cumandamentu cumpostu chì, 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 passi 1-3 sò una recuperazione IMDSv2 di manuale: dumandate un token di sessione, poi attaccatelu cum'è X-aws-ec2-metadata-token intestazione per estrarre u rolu IAM di l'istanza è e chjave d'accessu tempuranee di quellu rolu. A scelta di implementà IMDSv2 piuttostu chè l'IMDSv1 più simplice senza autenticazione GET hè da nutà - significa chì u payload funziona ancu nantu à istanze cunfigurate per richiede l'accessu à i metadati basatu annantu à i token, chì hè u hardening raccomandatu da AWS. E credenziali restituite da u passu 3 sò di corta durata. AccessKeyId/SecretAccessKey/Token tripletti cù ambitu à u rolu di l'istanza; tuttu ciò chì quellu rolu pò fà, u detentore di quelle chjave pò fà per a durata di vita di a credenziale.
I passi 4–6 allargiscenu a presa. U env U dump cattura tuttu ciò chì u prucessu di compilazione o di runtime hà ereditatu - in pratica hè quì chì i gettoni di u registru, e stringhe di cunnessione à a basa di dati è e chjave API si trovanu più spessu. U /app a lettura di i fugliali leghje finu à quindici fugliali d'applicazione fora node_moduli, chì pò a cunfigurazione di a superficia, .env schedari, o surghjente. Chjame di u passu 6 aws secretsmanager lista-sicreti in trè regioni; l'autenticazioni ottenute in i passi 1-3 sò esattamente ciò chì autentica quelle chjame, dunque a lettura IMDS è a catena di enumerazione di Secrets Manager si uniscenu in una sola escalation: rolu d'istanza → inventariu di sicreti gestiti. U passu 7 hè un accennu à l'inquadramentu di cattura di bandiera - quandu un HTB{…} Quandu a bandera hè truvata, hè mandata da sola, altrimenti a massa grezza raccolta hè divisa in quattru pezzi è mandata.
E versioni successive in u cluster portanu l'escalazione più in là. Piuttostu chè piantà si à un inventariu, analizanu a risposta IMDS, esportanu e chjave tempuranee cum'è AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_SESSION_TOKEN variabili d'ambiente, cunfirmate l'identità cù aws sts get-caller-identity, è dopu ripete ogni sicretu restituitu da lista-sicreti chiacchiari aws secretsmanager get-secret-value nantu à ognunu - recuperendu u cuntenutu sicretu, micca solu i so nomi. E stesse versioni leghjenu ancu i binari di bandiere sustituiti da u prucessu (/leghjebandiera è amichi) è pruvate una carica corsa contru à qualsiasi prughjettu Rust truvatu sottu /app, allargendu a racolta oltre e credenziali di u cloud in tuttu ciò chì l'ambiente di custruzzione espone.
Queste versioni successive si cunnettanu ancu in modu più aggressivu. In più di u nome host 12-hex è /app/node_modules verifiche, u payload ispeziona a cunfigurazione di u registru di pacchetti attivu è u percorsu di u cartulare di travagliu è esce in silenziu quandu indicanu un cuntestu d'analisi o di specchiu piuttostu chè un target in diretta. L'effettu cumminatu hè un payload chì ùn face nunda d'osservabile in a maiò parte di l'ambienti d'ispezione è esegue a so cullezzione cumpleta solu induve si ghjudica esse nantu à un veru host containerizatu.
EsfiltrazioneI dati raccolti lascianu l'ospite per dui canali. Prima, una balisa POST à un fissu webhook.site cullettore, chì porta u nome di l'ospite, l'UID numericu, u cartulare di travagliu è finu à 120 KB di dati raccolti. Siconda, i dati sò piegati in un falsu "manifestu di modulu" YAML è Mastru à /api/moduli/ nant'à un servitore 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 di i campi di manifestu (livellu_di_putenza, ponte_di_nave, stiva_di_caricu) sò decorazioni - i dati arrubati si trovanu in i valori di stringa, hè per quessa chì un monitor di rete vede ciò chì pare un caricamentu benignu di manifestu di registru di pacchetti piuttostu chè un dump di dati evidente. U canale beacon porta di più: u POST corpu à webhook.site include u nome di l'ospite, l'UID numericu, u cartulare di travagliu è finu à 120 KB di u blob raccoltu, dunque ancu un solu beacon riesciutu furnisce a presa cumpleta. webhook.site hè un serviziu gratuitu d'ispezione di richieste; aduprà lu cum'è cullettore significa chì l'operatore ùn hà mai bisognu di stà in piedi per riceve a so propria infrastruttura per quellu canale, è e richieste registrate persistenu in u cestino di u serviziu.
U manifestu Mastru cammina una lista di fallback chì principia cù parechji 127.0.0.1/localhost porti è dopu casca à trè indirizzi publichi in u `154.57.164.0/24` gamma, fermendusi à u primu endpoint chì risponde cù un statutu 2xx. L'ordine localhost-first hè coerente cù l'autodescrizzione di "verdaccio testing" (un registru lucale in loopback), ma i fallbacks public-IP significanu chì i dati lascianu l'host ogni volta chì u loopback ùn hè micca à l'ascolta - vale à dì, nantu à qualsiasi macchina chì ùn hè micca u bancu di prova di l'autore.
Timeline
U cluster mostra una crescita incrementale di capacità piuttostu chè una sola diminuzione. L'urdinemu per cumpurtamentu osservatu, micca per clock di publicazione:
| Stage | Pacchetti / Versioni | Cumpurtamentu |
|---|---|---|
| Corsa anticipata | coral-wraith 9999.0.x | Numeri di versione gonfiati coerenti cù un tentativu di cunfusione di dipendenze; enumerazione à u mumentu di l'installazione è exfil |
| Seed | coral-wraith 1.0.0 | A postinstallazione raccoglie i fugliali id/env/flag; un unicu PUT à 154[.]57[.]164[.]71:30782marcatore ECT-472839 |
| Iterazione rapida | coral-wraith 1.0.1 → 6.0.0 | Decine di liberazioni in ore; u payload guadagna u isAppWorker() porta, estrazione di credenziali IMDSv2, u cumpletu get-secret-value pivot, a verificazione di l'ambiente di u registru/percorsu, è i marcatori di doppiu sink |
| Nomi paralleli | ecto-corsair-whisper-6f3b9 1.0.14–1.0.18 | Stessa carica utile gated; webhook.site lista di beacon è di fallback multi-endpoint |
| Varianti | ecto-rust-read-f3a9c1 1.0.1–1.0.2 | Aghjusta marcatori di lavandinu supplementari ECT-987654, ECT-654321, ECT-839201 |
| Varianti | ecto-corsair-flag-x9m4 1.0.0, ecto-nightly-spirit 1.1.0 | Stessa carica utile gated, stessa C2 è beacon |
A caratteristica definitoria di u cluster hè a so cadenza di publicazione: invece di un pacchettu è una versione, u listessu nome hè ripublicatu più è più volte in rapida successione, ogni versione una piccula variazione di l'ultima, accantu à una manata di fratelli cù nomi diversi chì portanu u listessu payload. Dentru à u sussuru famiglia u codice hè divisu in duie impronte digitali strette - un inseme chì attiva duie rilevazioni critiche, un altru trè (un sink di lettura di file supplementu) - ma tramindui si risolvenu in u listessu payload; a differenza hè a deriva di u codice, micca una furchetta cumportamentale. Versioni di sussuru fora di l'intervallu analizatu (finu à almenu 1.0.25 à u mumentu di a scrittura) sò stati osservati in diretta nantu à u registru, è u corallo-spettru u nome hà cuntinuatu à cullà a so propria scala di versione sopra a listessa finestra.
Indicatori di compromessi
Tutti l'indicatori quì sottu sò stati estratti da a fonte di u pacchettu nantu à u discu. L'indicatori di rete sò stati disattivati.
Network
| pressure | rolu |
|---|---|
hxxp://154[.]57[.]164[.]71:30782 | Obiettivu C2 PUT (coral-wraith) |
hxxp://154[.]57[.]164[.]80:30543 | Ritornu C2 PUT (ecto-*) |
hxxp://154[.]57[.]164[.]82:31250 | Ritornu C2 PUT (ecto-*) |
hxxp://154[.]57[.]164[.]71:31289 | Ritornu C2 PUT (ecto-*) |
hxxps://webhook[.]site/602a4c72-7033-4e28-92ea-dc66e59206e5 | Cullettore di fari |
169[.]254[.]169[.]254/latest/... | Lettura di credenziali IMDSv2 (latu di destinazione, metadati AWS) |
Cumportamentale/schedariu
| pressure | rolu |
|---|---|
"postinstall": "node postinstall.js" | Installà u vettore |
ecto_module: YAML cù power_level / ship_deck / cargo_hold chjavi | Schema di manifestu Exfil |
Marcatori di lavandini ECT-472839, ECT-987654, ECT-654321, ECT-839201 | Segmentu di percorsu C2 /api/modules/<marker> |
isAppWorker() porta: ospite /^[0-9a-f]{12}$/, cwd cuntene /app/node_modules | Cundizione d'attivazione |
aws secretsmanager list-secrets nantu us-east-1, eu-west-1, eu-central-1 | Enumerazione di sicreti |
HTB{...} raschiatura regex | Raccolta di cattura di bandiera |
Hash di file (sha256, catturati à u mumentu di l'analisi)
| File | sha256 |
|---|---|
coral-wraith/postinstall.js | ce5ff035cfdfed1d0015446424b352c27b66bcb77e9fdb0a51e4245199146824 |
ecto-corsair-whisper-6f3b9 1.0.18/postinstall.js | b58432acba376aa6976f0490d9a1c04257ccdbc856d8390260c50322d63e31c3 |
Attribuzione è cumpurtamentu osservatu
I cinque nomi di pacchetti sò stati publicati sottu à dui handle di contu npm, ma spartenu abbastanza infrastruttura per trattalli cum'è un cluster: u listessu ecto_module schema manifestu, listessu ECT-472839 marcatore di lavandinu primariu, listessu webhook.site ID di u cullettore, è punti finali C2 in u listessu Bloccu 154.57.164.0/24`. U pacchettu di sementi (`coral-wraith, più simplice è senza gating) è l'ecto-famiglia gated cusì letta cum'è iterazioni nantu à un kit di strumenti piuttostu chè sforzi indipendenti.
I pacchetti si descrivenu, in versioni successive, cum'è "Carica utile CTF per i testi di a catena di furnimentu di Verdaccio." Facemu vene sta etichetta cum'è un fattu osservabile è ùn a ripetemu micca cum'è una scuperta nantu à u scopu. Ciò chì face u codice hè senza ambiguità è indipendente da cumu hè etichettatu: legge e credenziali di u rolu IAM da u serviziu di metadati di l'istanza, enumera i sicreti gestiti in trè regioni AWS è trasmette i risultati à un IP publicu è à un cullettore di webhook di terze parti. Un veru cablaggio di test solu loopback ùn averebbe bisognu di a lista di fallback di IP publicu, di e letture di credenziali IMDS o di e chjame di Secrets Manager interregionale. Siccome l'uscita è a portata di e credenziali sò reali, e versioni gated sò state classificate cum'è maliziose.
A porta solu per i container hè a caratteristica più notevule da un puntu di vista operativu. Hè sia una misura d'evasione - stà zittu nantu à i laptop è in e sandbox d'analisi - sia una misura di mira, chì si attiva solu induve hè più prubabile chì un veru rolu IAM è secreti in diretta sianu presenti. L'analisti chì eseguiscenu questi pacchetti in una sandbox generica ùn osserverebbenu nunda; u cumpurtamentu si manifesta solu sottu un nome d'ospite in stile Docker è un percorsu d'installazione in container.
Impattu, tendenze è guida per i difensori
L'esposizione quì hè a divulgazione di credenziali in u cloud è di secreti in i container di compilazione è di runtime. Una credenziale di rolu IAM estratta da IMDS porta qualsiasi permessu chì quellu rolu detiene; gestore di secreti: Lista di secreti (è ogni seguitu GetSecretValue) allarga questu à i sicreti di l'applicazione almacenati. I dumps di variabili d'ambiente portanu spessu gettoni di registru, URL di basa di dati è chjave API. In un cuntestu CI o di container - esattamente ciò chì a porta selezziuna - una sola installazione transitiva di unu di sti pacchetti hè abbastanza per filtrà quellu materiale.
Ectoplasm currisponde à un mudellu chì cuntinuemu à vede: carichi utili in tempu d'installazione chì cercanu metadati in u cloud è sicreti gestiti invece di fugliali lucali, è chì si attivanu da soli per esse attivati solu in ambienti di altu valore. Seguenu duie osservazioni difensive.
- A forma hè rilevabileUn hook d'installazione npm/PyPI chì u so graficu di chjama righjunghje sia una API di secreti di u cloud (gestore di secreti aws, sicreti di gcloud, az keyvault) o l'indirizzu IMDS è un dissipatore di uscita di a rete hè un mudellu strettu è di segnale altu - ùn si verifica guasi mai in un script di ciclu di vita legittimu. Analisi di flussu staticu pò signalallu senza dipende da alcun duminiu o IP specificu.
- L'indurimentu di l'ambiente l'imbrutisce. L'applicazione di IMDSv2 cù un limite di hop di 1 impedisce à i carichi di travagliu di i container di ghjunghje à i metadati di l'istanza; l'ambitu di i roli IAM à u privilegiu minimu limita u raghju di blast di qualsiasi credenziale chì perde; è l'esecuzione di installazioni cù –Ignore-scripts in CI elimina cumpletamente u vettore install-hook per i pacchetti chì ùn ne anu micca bisognu.
Per i difensori, i cuntrolli pratichi sò: alerta nantu à e cunnessione in uscita da i container di compilazione/CI à l'IP publichi micca listati durante npm install; survegliate l'accessu IMDS chì vene da i script di u ciclu di vita di i pacchetti; è trattate qualsiasi hook d'installazione chì si spedisce à una CLI in u cloud cum'è suspettu finu à chì ùn sia pruvatu u cuntrariu.
Dui altre note specifiche per questu cluster. Prima, postu chì l'attivazione hè limitata à l'ambienti containerizzati, un pacchettu chì appare inerte quandu hè verificatu nantu à una stazione di travagliu pò ancu esse in diretta in pruduzzione - a verificazione deve riproduce u nome di l'ospite di u container è e cundizioni di u percorsu, o leghje direttamente a fonte, invece di fidà si di "L'aghju installatu è ùn hè accadutu nunda". Siconda, l'usu di un serviziu publicu di ispezione di richieste cum'è cullettore di beacon significa chì alcuni di i dati esfiltrati ponu esse recuperabili per a risposta à l'incidenti: una urganizazione chì trova unu di questi pacchetti in u so arburu di dipendenze pò ragiunà nantu à ciò chì un beacon riesciutu averia cuntenutu da a logica di cullezzione di u payload, è deve rotà qualsiasi credenziali di rolu IAM, token di registru è secreti gestiti chì eranu accessibili da l'ambiente di compilazione o di runtime affettatu. Rotazione di credenziali, micca a rimuzione di u pacchettu, hè a riparazione operativa una volta chì una installazione hè stata eseguita.





