Tl; DR
Isang kumpol ng limang pakete ng npm, na inilathala sa dalawang hawakan ng account, nagpadala ng postinstall hook na nagbabasa ng mga kredensyal sa cloud mula sa host at ipinapadala ang mga ito palabas ng kahon. Ang mga pakete ay may mga pangalang parang multo at pirata — coral-wraith, ecto-corsair-whisper-6f3b9, ecto-corsair-flag-x9m4, ecto-rust-read-f3a9c1, ecto-nightly-spirit — at gawing peke ang anumang kinokolekta nila sa serye ecto_module: Manifest ng YAML bago ito ipadala. Sinusubaybayan namin ang cluster bilang ectoplasm.
Tumatakbo lamang ang payload kapag nakakita ito ng isang partikular na kapaligiran: isang host na ang pangalan ay isang 12-karakter na heksagonal na string at isang gumaganang direktoryo sa ilalim ng /app/node_modules — ang hugis ng isang containerized build o CI worker. Kapag dumaan ang gate na iyon, kinukuwestiyon ng hook ang Serbisyo ng metadata ng AWS instance (IMDSv2) para sa mga kredensyal sa tungkulin ng IAM, inililista AWS Secrets Manager sa tatlong rehiyon, nagtatapon ng mga environment variable, nagbabasa ng mga file sa ilalim ng /app, at kumukuha ng mga kuwerdas para sa pagkuha ng bandila. Pagkatapos ay inilalabas nito ang resulta sa dalawang paraan: isang beacon patungo sa isang webhook.site kolektor at isang manifest na PUT sa isang raw-IP endpoint, na may listahan ng mga localhost-first fallback.
Ang mga huling deskripsyon ng pakete ay mababasa bilang "CTF payload para sa verdaccio supply-chain testing." Iniuulat namin ang paglalarawan sa sarili bilang isang napapansing katotohanan. Ang mismong kilos — direktang paglabas sa isang pampublikong IP, pagbabasa ng totoong kredensyal ng IMDS, pagtawag ng totoong Secrets Manager — ay kung ano ito anuman ang label, at ito ang dahilan kung bakit inuri ang mga bersyong ito bilang malisyoso.
Isang pangalan sa kumpol, coral-wraith, ay hindi tumigil sa isang paglabas lamang. Ito ay muling inilathala nang mabilis at sunod-sunod sa dose-dosenang mga bersyon sa loob lamang ng ilang oras — 1.0.0 pag-akyat sa 6.0.0 — at ang naunang paggamit ng parehong pangalan ay gumamit ng inflated 9999.0.x mga numero ng bersyon, ang klasikong hugis ng isang pagtatangkang magdulot ng pagkalito dahil sa dependencySa kabila nito, ang payload ay kitang-kitang hinog na: mula sa isang one-shot host-enumeration beacon patungo sa isang kumpletong AWS credential pivot na binalot ng mga environment check na nagpapanatiling tahimik dito sa labas ng nilalayon nitong target.
| Pakete | 5 pangalan; coral-wraith nag-iisa na muling inilathala sa dose-dosenang mga bersyon |
| ecosystem | npm |
| I-install ang vector | postinstall iskrip ng siklo ng buhay |
| Pangunahing target | Mga kredensyal sa tungkulin ng AWS IAM + mga halaga ng sikreto ng Secrets Manager, mga variable ng kapaligiran, /app file |
| Exfil | webhook.site beacon + raw-IP C2 PUT |
| Gate ng gatilyo | 12-hex na pangalan ng host + /app/node_modules cwd, kasama ang isang environment check na pumipigil sa payload sa labas ng kontekstong iyon |
| Tindi | mataas — kredensyal sa cloud at pagsisiwalat ng pinamamahalaang lihim mula sa mga containerized na build at runtime environment |
Anatomiya ng pag-atake
Ang bawat pakete sa kumpol ay binubuo sa parehong paraan: isang halos walang laman index.js (module.exports = {}), isang linya package.json iskrip — "postinstall": "node postinstall.js" — at ang kargamento sa postinstall.js. Ang pag-install ng package ay sapat na upang patakbuhin ang hook; hindi na kailangan ng import o call.
Ang tarangkahan ng pag-target. Bago gumawa ng anuman, sinusuri ng ecto-family payload ang paligid nito:
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 Ang 12-hex hostname ay ang default na hugis na itinatalaga ng Docker sa isang container, at /app/node_modules ay isang kumbensyonal na in-container install path. Ang ikatlong sugnay ay mawawala kung ang path ay mukhang isang sandboxed extraction directory. Ang netong epekto ay ang payload ay nananatiling hindi aktibo sa isang developer laptop o isang analysis sandbox at nag-a-activate lamang sa loob ng isang containerized build o runtime worker — ang uri ng kapaligirang malamang na magdala ng mga live cloud credential. Ang pinakamaagang package sa cluster, korales na multo, ay walang ganoong gate at pinapatakbo ang (mas simple) nitong koleksyon nang walang kondisyon.
koleksyonKapag dumaan ang gate, ang kawit ay lumalabas execFileSync(“/bin/sh”, [“-c”, …]) at nagpapatakbo ng isang solong compound command na, sa pagkakasunud-sunod:
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) Ang mga hakbang 1–3 ay isang aklat-aralin para sa pagkuha ng IMDSv2: humiling ng token ng sesyon, pagkatapos ay ilakip ito bilang Token ng X-aws-ec2-metadata header para kunin ang IAM role ng instance at ang mga pansamantalang access key ng role na iyon. Ang pagpipiliang ipatupad ang IMDSv2 sa halip na ang mas simple at hindi awtorisadong IMDSv1 GET ay mahalagang tandaan — nangangahulugan ito na gumagana ang payload kahit sa mga instance na na-configure upang mangailangan ng token-based metadata access, na siyang hardening na inirerekomenda ng AWS. Ang mga kredensyal na ibinalik sa hakbang 3 ay panandalian lamang. AccessKeyId/SecretAccessKey/Token mga triple na nakasaklaw sa tungkulin ng instance; anuman ang magagawa ng tungkuling iyon, magagawa ng may-ari ng mga key na iyon habang buhay ang kredensyal.
Pinalalawak ng mga Hakbang 4–6 ang daanan. Ang env Kinukuha ng dump ang anumang minana ng proseso ng build o runtime — sa pagsasagawa dito kadalasang nabubuhay ang mga registry token, mga string ng koneksyon sa database, at mga API key. /app Ang file walk ay nagbabasa ng hanggang labinlimang application file sa labas node_modules, na maaaring mag-ibabaw ng konfigurasyon, .env mga file, o pinagmulan. Mga tawag sa Hakbang 6 listahan ng mga lihim ng aws secretsmanager sa tatlong rehiyon; ang mga kredensyal na nakuha sa mga hakbang 1–3 ang siyang eksaktong nagpapatotoo sa mga tawag na iyon, kaya ang IMDS ay nagbabasa at ang Secrets Manager enumeration chain ay pinagsama-sama sa isang escalation: instance role → managed-secret inventory. Ang Hakbang 7 ay isang pagsang-ayon sa capture-the-flag framing — kapag ang isang HTB{…} Kung matagpuan ang bandila, ito ay ipapadala nang mag-isa, kung hindi, ang hilaw na nakolektang patak ay hahatiin sa apat na piraso at ipapadala.
Ang mga mas bagong bersyon sa cluster ay nagdadala ng karagdagang escalation. Sa halip na huminto sa isang imbentaryo, sinusuri nila ang tugon ng IMDS, ini-export ang mga pansamantalang susi bilang AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_SESSION_TOKEN mga baryabol ng kapaligiran, kumpirmahin ang pagkakakilanlan gamit ang aws sts get-caller-identity, at pagkatapos ay balikan ang bawat sikretong ibinalik ni mga lihim ng listahan pagtatawag aws secretsmanager kumuha ng lihim na halaga sa bawat isa — kinukuha ang mga sikretong nilalaman, hindi lamang ang kanilang mga pangalan. Binabasa rin ng parehong mga bersyon ang mga binary ng flag na pinalitan ng proseso (/readflag at mga kaibigan) at subukan ang isang charge run laban sa anumang proyektong Rust na matatagpuan sa ilalim ng /app, pagpapalawak ng ani na lampas sa mga kredensyal sa cloud sa anumang inilalantad ng kapaligiran sa pagtatayo.
Ang mga mas bagong bersyong ito ay mas agresibo ring nag-gate sa kanilang mga sarili. Bukod pa sa 12-hex-hostname at /app/node_modules Kapag sinusuri, sinusuri ng payload ang aktibong configuration ng package-registry at ang path ng working-directory at tahimik na lumalabas kapag ipinahiwatig ng mga ito ang isang analysis o mirror context sa halip na isang live target. Ang pinagsamang epekto ay isang payload na walang ginagawa na naoobserbahan sa karamihan ng mga environment ng inspeksyon at pinapatakbo lamang ang buong koleksyon nito kung saan hinuhusgahan nito ang sarili na nasa isang tunay na containerized host.
ExfiltrationAng nakalap na datos ay umaalis sa host sa dalawang channel. Una, isang beacon POST sa isang nakapirming webhook.site kolektor, dala ang hostname, numeric UID, working directory, at hanggang 120 KB ng nakolektang data. Pangalawa, ang data ay tinitiklop sa isang pekeng YAML na "module manifest" at PUT sa /api/mga modyul/ sa isang target na server:
ecto_module: name: "<flag-or-chunk-0>" version: "1.0.0" power_level: "<chunk-1>" ship_deck: "<chunk-2>" cargo_hold: "<chunk-3>" Ang mga pangalan ng manifest field (antas_ng_kapangyarihan, kubyerta ng barko, cargohold) ay dekorasyon — ang ninakaw na data ay nasa loob ng mga string value, kaya naman nakakakita ang isang network monitor ng mukhang isang hindi masamang package-registry manifest upload sa halip na isang halatang data dump. Ang beacon channel ay nagdadala ng higit pa: ang POST katawan sa webhook.site Kasama rito ang hostname, numeric UID, working directory, at hanggang 120 KB ng nakolektang blob, kaya kahit isang matagumpay na beacon ay maihahatid ang buong pagkuha. webhook.site ay isang libreng serbisyo ng inspeksyon ng kahilingan; ang paggamit nito bilang isang kolektor ay nangangahulugan na ang operator ay hindi na kailangang humawak ng sarili nilang imprastraktura ng pagtanggap para sa channel na iyon, at ang mga naitalang kahilingan ay mananatili sa bin ng serbisyo.
Ang manifest PUT naglalakad sa isang fallback list na nagsisimula sa ilan 127.0.0.1/localhost mga port at pagkatapos ay nahuhulog sa tatlong pampublikong address sa `154.57.164.0/24` range, na humihinto sa unang endpoint na sumasagot nang may status na 2xx. Ang localhost-first ordering ay naaayon sa self-description na "verdaccio testing" (isang local registry sa loopback), ngunit ang mga public-IP fallback ay nangangahulugan na ang data ay umaalis sa host tuwing hindi nakikinig ang loopback — na ibig sabihin, sa anumang makina na hindi sariling test rig ng may-akda.
timeline
Ang kumpol ay nagpapakita ng unti-unting paglago ng kakayahan sa halip na isang pagbaba lamang. Inaayos namin ito ayon sa naobserbahang pag-uugali, hindi ayon sa paglalathala ng orasan sa dingding:
| Stage | Mga Pakete / Bersyon | Pag-uugali |
|---|---|---|
| Maagang pagtakbo | coral-wraith 9999.0.x | Pinalaking mga numero ng bersyon na naaayon sa isang pagtatangkang magkamali dahil sa dependency; pagbilang sa oras ng pag-install at exfil |
| Magbigay ng binhi | coral-wraith 1.0.0 | Kinokolekta ng postinstall ang mga id/env/flag file; iisang PUT ang 154[.]57[.]164[.]71:30782, pananda ECT-472839 |
| Mabilis na pag-ulit | coral-wraith 1.0.1 → 6.0.0 | Dose-dosenang mga paglabas sa loob ng ilang oras; ang payload ay nakakakuha ng isAppWorker() gate, IMDSv2 credential pull, ang buong get-secret-value pivot, ang pagsusuri sa kapaligiran ng registry/path, at mga dual sink marker |
| Mga pangalang magkapareho | ecto-corsair-whisper-6f3b9 1.0.14–1.0.18 | Parehong naka-gate na kargamento; webhook.site listahan ng fallback ng beacon at multi-endpoint |
| Variant | ecto-rust-read-f3a9c1 1.0.1–1.0.2 | Nagdaragdag ng mga karagdagang marker sa lababo ECT-987654, ECT-654321, ECT-839201 |
| Variant | ecto-corsair-flag-x9m4 1.0.0, ecto-nightly-spirit 1.1.0 | Parehong gated payload, parehong C2 at beacon |
Ang natatanging katangian ng kumpol ay ang ritmo ng paglalathala nito: sa halip na isang pakete at isang bersyon, ang parehong pangalan ay paulit-ulit na inilalathala nang mabilis, bawat isa ay naglalabas ng isang maliit na pagkakaiba-iba sa huli, kasama ang isang maliit na magkakapatid na may iba't ibang pangalan na may parehong kargamento. Sa loob ng bumulong pamilya ang code ay nahahati sa dalawang malapitang fingerprint — isang set na nagti-tripping ng dalawang kritikal na detection, isa pang tatlo (isang karagdagang file-read sink) — ngunit parehong nagre-resolve sa parehong payload; ang pagkakaiba ay code drift, hindi isang behavioral fork. Mga Bersyon ng bumulong lampas sa nasuring saklaw (hanggang sa hindi bababa sa 1.0.25 sa panahon ng pagsulat) ay naobserbahan nang live sa registry, at ang korales na multo patuloy na umakyat ang pangalan sa sarili nitong bersyon sa parehong bintana.
Mga tagapagpahiwatig ng kompromiso
Ang lahat ng indicator sa ibaba ay kinuha mula sa pinagmulan ng package sa disk. Hindi na ginagamit ang mga network indicator.
network
| Nagtuturo | Papel |
|---|---|
hxxp://154[.]57[.]164[.]71:30782 | Target ng C2 PUT (coral-wraith) |
hxxp://154[.]57[.]164[.]80:30543 | C2 ILAGAY ang fallback (ecto-*) |
hxxp://154[.]57[.]164[.]82:31250 | C2 ILAGAY ang fallback (ecto-*) |
hxxp://154[.]57[.]164[.]71:31289 | C2 ILAGAY ang fallback (ecto-*) |
hxxps://webhook[.]site/602a4c72-7033-4e28-92ea-dc66e59206e5 | Kolektor ng parola |
169[.]254[.]169[.]254/latest/... | Pagbasa ng kredensyal ng IMDSv2 (target-side, metadata ng AWS) |
Pang-asal/file
| Nagtuturo | Papel |
|---|---|
"postinstall": "node postinstall.js" | I-install ang vector |
ecto_module: YAML kasama power_level / ship_deck / cargo_hold mga susi | Eskema ng manifest ng Exfil |
Mga marker ng lababo ECT-472839, ECT-987654, ECT-654321, ECT-839201 | Bahagi ng landas na C2 /api/modules/<marker> |
isAppWorker() gate: host /^[0-9a-f]{12}$/, naglalaman ang cwd /app/node_modules | Kondisyon ng pag-activate |
aws secretsmanager list-secrets sa ibabaw us-east-1, eu-west-1, eu-central-1 | Pag-enumerasyon ng mga lihim |
HTB{...} regex scrape | Pag-aani ng paghuli sa bandila |
Mga hash ng file (sha256, nakuha sa oras ng pagsusuri)
| talaksan | sha256 |
|---|---|
coral-wraith/postinstall.js | ce5ff035cfdfed1d0015446424b352c27b66bcb77e9fdb0a51e4245199146824 |
ecto-corsair-whisper-6f3b9 1.0.18/postinstall.js | b58432acba376aa6976f0490d9a1c04257ccdbc856d8390260c50322d63e31c3 |
Pagpapatungkol at naobserbahang pag-uugali
Ang limang pangalan ng pakete ay inilathala sa ilalim ng dalawang npm account handle, ngunit mayroon silang sapat na imprastraktura upang ituring ang mga ito bilang isang kumpol: pareho ecto_module manifest schema, pareho ECT-472839 pangunahing marker ng lababo, pareho webhook.site collector ID, at mga C2 endpoint sa parehong 154.57.164.0/24` na bloke. Ang pakete ng binhi (`coral-wraith, mas simple at walang gate) at ang gated ecto-family samakatuwid ay binabasa bilang mga iterasyon sa isang toolkit sa halip na mga independiyenteng pagsisikap.
Inilalarawan ng mga pakete ang kanilang mga sarili, sa mga susunod na bersyon, bilang "Kargamento ng CTF para sa pagsubok sa supply-chain ng Verdaccio." Inilalantad namin ang label na iyan bilang isang napapansing katotohanan at hindi namin ito inuulit bilang isang natuklasan tungkol sa layunin. Ang ginagawa ng code ay malinaw at independiyente sa kung paano ito nilagyan ng label: binabasa nito ang mga kredensyal ng papel ng IAM mula sa serbisyo ng metadata ng instance, inililista ang mga pinamamahalaang lihim sa tatlong rehiyon ng AWS, at ipinapadala ang mga resulta sa isang pampublikong IP at isang third-party na webhook collector. Ang isang tunay na loopback-only test harness ay hindi mangangailangan ng listahan ng public-IP fallback, pagbabasa ng kredensyal ng IMDS, o mga tawag ng Secrets Manager sa buong rehiyon. Dahil ang egress at credential reach ay totoo, ang mga gated na bersyon ay inuri bilang malicious.
Ang container-only gate ang pinakakapansin-pansing katangian sa operasyon. Ito ay parehong isang hakbang sa pag-iwas — pananatiling tahimik sa mga laptop at sa mga analysis sandbox — at isang hakbang sa pag-target, na pinapagana lamang kung saan malamang na naroroon ang isang tunay na papel ng IAM at mga live na sikreto. Ang mga analyst na nagpapatakbo ng mga paketeng ito sa isang generic na sandbox ay walang mapapansin; ang pag-uugali ay makikita lamang sa ilalim ng isang Docker-style na hostname at isang in-container install path.
Epekto, mga uso at gabay para sa mga tagapagtanggol
Ang exposure dito ay cloud-credential at sikretong pagsisiwalat sa loob ng mga build at runtime container. Ang isang IAM role credential na kinuha mula sa IMDS ay nagtataglay ng anumang mga pahintulot na hawak ng role na iyon; secretsmanager:ListMga Lihim (at anumang kasunod na Kunin ang Lihim na Halaga) ay nagpapalawak nito sa mga nakaimbak na sikreto ng aplikasyon. Ang mga environment-variable dumps ay kadalasang nagdadala ng mga registry token, database URL, at API key. Sa konteksto ng CI o container — eksakto kung para saan pinipili ang gate — ang isang transitive install ng isa sa mga package na ito ay sapat na upang mailabas ang materyal na iyon.
Ang Ectoplasm ay umaakma sa isang huwarang patuloy nating nakikita: mga payload sa oras ng pag-install na umaabot sa metadata ng cloud at mga pinamamahalaang sikreto sa halip na mga lokal na file, at ang mga ito mismo ay nagkokonekta sa mga kapaligirang may mataas na halaga. Kasunod nito ay dalawang depensibong obserbasyon.
- Mapapansin ang hugisIsang npm/PyPI install hook na ang call graph ay umaabot sa parehong cloud secrets API (tagapamahala ng mga lihim ng aws, mga sikreto ng gcloud, az keyvault) o ang IMDS address at ang network egress sink ay isang makitid at mataas na signal pattern — halos hindi ito nangyayari sa isang lehitimong lifecycle script. Pagsusuri ng istatikong daloy maaari itong i-flag nang hindi umaasa sa anumang partikular na domain o IP.
- Pinapahina ito ng pagtigas ng kapaligiran. Ang pagpapatupad ng IMDSv2 na may hop limit na 1 ay pumipigil sa mga workload ng container na maabot ang metadata ng instance; ang pag-scope ng mga IAM roles sa least privilege ay naglilimita sa blast radius ng anumang credential na nagleak; at pagpapatakbo ng mga install gamit ang –Hindi pansinin-ang mga script sa CI, ganap na inaalis ang install-hook vector para sa mga pakete na hindi nangangailangan nito.
Para sa mga tagapagtanggol, ang mga praktikal na pagsusuri ay: alerto sa mga papalabas na koneksyon mula sa mga build/CI container patungo sa mga hindi pinapayagang pampublikong IP habang install npm; bantayan ang access sa IMDS na nagmumula sa mga script ng lifecycle ng package; at ituring na pinaghihinalaan ang anumang install hook na gumagamit ng cloud CLI hanggang sa mapatunayang hindi.
Dalawa pang tala na partikular sa cluster na ito. Una, dahil ang activation ay naka-gated sa mga containerized environment, ang isang package na mukhang inert kapag na-vet sa isang workstation ay maaari pa ring live in production — kailangang kopyahin ng vetting ang container hostname at path conditions, o basahin nang direkta ang source, sa halip na umasa sa "Na-install ko na ito at walang nangyari." Pangalawa, ang paggamit ng pampublikong request-inspection service bilang beacon collector ay nangangahulugan na ang ilan sa mga exfiltrated data ay maaaring mabawi para sa incident response: ang isang organisasyon na makakahanap ng isa sa mga package na ito sa dependency tree nito ay maaaring mangatwiran tungkol sa kung ano ang maaaring nilalaman ng isang matagumpay na beacon mula sa collection logic ng payload, at dapat i-rotate ang anumang IAM role credentials, registry tokens, at managed secrets na maaabot mula sa apektadong build o runtime environment. Pag-ikot ng kredensyal, hindi ang pag-alis ng pakete, ang operative remediation kapag natapos na ang isang install.





