TL; DR
Ett kluster av fem npm-paket, publicerad över två kontonamn, skickad en postinstall en krok som läser molnuppgifter från värden och skickar dem direkt från lådan. Paketen har spök- och piratnamn — coral-wraith, ecto-corsair-whisper-6f3b9, ecto-corsair-flag-x9m4, ecto-rust-read-f3a9c1, ecto-nightly-spirit — och serialisera vad de än samlar in till en förfalskning ecto_module: YAML-manifestet innan det skickas. Vi spårar klustret som ektoplasma.
Nyttolasten körs bara när den upptäcker en specifik miljö: en värd vars namn är en 12-teckens hexagonsträng och en arbetskatalog under /app/node_modules — formen på en containeriserad byggnad eller CI-arbetare. När den grinden passerar, frågar kroken efter AWS-instansmetadatatjänst (IMDSv2) för IAM-rolluppgifter, räknar upp AWS Secrets Manager över tre regioner, dumpar miljövariabler, läser filer under /appoch letar efter strängar för att erövra flaggan. Den filtrerar sedan ut resultatet på två sätt: en beacon till en webhook.site samlare och en manifest-PUT till en rå-IP-slutpunkt, med en lista över localhost-first-fallbacks.
De senare paketbeskrivningarna lyder "CTF-nyttolast för Verdaccio-testning av leveranskedjor". Vi rapporterar den självbeskrivningen som ett observerbart faktum. Själva beteendet – live-utgång till en offentlig IP, verkliga IMDS-autentiseringsuppgifter, verkliga Secrets Manager-anrop – är vad det är oavsett etikett, och är anledningen till att dessa versioner klassificerades som skadliga.
Ett namn i klustret, coral-wraith, stannade inte vid en enda utgåva. Den publicerades på nytt i snabb följd genom dussintals versioner på bara några timmar — 1.0.0 klättra till 6.0.0 — och en tidigare omgång med samma namn hade använt uppblåsta 9999.0.x versionsnummer, den klassiska formen på en försök till beroendeförvirringGenom den omvälvningen mognade nyttolasten synbart: från en engångs-värduppräkningsfyr till en fullständig AWS-autentiseringsuppgifts pivot insvept i miljökontroller som håller den tyst utanför sitt avsedda mål.
| Paket | 5 namn; coral-wraith ensam återpublicerad i dussintals versioner |
| Ekosystem | npm |
| Installera vektor | postinstall livscykelskript |
| Primärt mål | AWS IAM-rolluppgifter + hemliga värden för Secrets Manager, miljövariabler, /app filer |
| Exfil | webhook.site beacon + rå-IP C2 PUT |
| Utlösargrind | 12-hex värdnamn + /app/node_modules cwd, plus en miljökontroll som undertrycker nyttolasten utanför den kontexten |
| Svårighetsgraden | hög — molnuppgifter och hanterad hemlighetsutlämnande från containeriserade bygg- och runtime-miljöer |
Attackanatomi
Varje paket i klustret är uppbyggt på samma sätt: ett nästan tomt index.js (modul.exporter = {}), en enradig package.json manus — "postinstall": "nod postinstall.js" — och nyttolasten i postinstall.jsDet räcker med att installera paketet för att köra hooken; ingen import eller anrop krävs.
Målgrinden. Innan någonting görs kontrollerar ektofamiljens nyttolast sin omgivning:
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 Ett 12-hex-värdnamn är standardformen som Docker tilldelar en container, och /app/node_modules är en konventionell installationssökväg i en container. Den tredje klausulen undviks om sökvägen ser ut som en sandlådebaserad extraktionskatalog. Nettoeffekten är att nyttolasten förblir vilande på en utvecklarbärbar dator eller en analyssandlåda och bara aktiveras inuti en containeriserad build eller runtime-worker – den typ av miljö som är mest sannolikt att ha live-molnuppgifter. Det tidigaste paketet i klustret, korallvålnad, har ingen sådan grind och driver sin (enklare) samling villkorslöst.
SamlingNär grinden passerar, sticker kroken ut genom execFileSync("/bin/sh", ["-c", …]) och kör ett enda sammansatt kommando som, i ordning:
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) Steg 1–3 är en lärobok för IMDSv2-hämtning: begär en sessionstoken och bifoga den sedan som X-aws-ec2-metadata-token header för att hämta instansens IAM-roll och den rollens tillfälliga åtkomstnycklar. Valet att implementera IMDSv2 snarare än den enklare oautentiserade IMDSv1 FÅ är värt att notera — det betyder att nyttolasten fungerar även på instanser som är konfigurerade för att kräva tokenbaserad metadataåtkomst, vilket är den AWS-rekommenderade härdningen. De autentiseringsuppgifter som returneras av steg 3 är kortlivade. AccessKeyId/Hemlig åtkomstnyckel/Pollett tripplar begränsade till instansens roll; vad den rollen än kan göra, kan innehavaren av dessa nycklar göra under autentiseringsuppgifternas livstid.
Steg 4–6 breddar perspektivet. env dump fångar allt som bygg- eller körningsprocessen ärvt — i praktiken är det här registertokens, databasanslutningssträngar och API-nycklar oftast finns. / app filpromenad läser upp till femton applikationsfiler utanför node_modules, vilket kan ytkonfiguration, .env filer eller källa. Steg 6 anropar aws secretsmanager lista-hemligheter i tre regioner; autentiseringsuppgifterna som hämtas i steg 1–3 är exakt det som autentiserar dessa anrop, så IMDS-läsningen och Secrets Manager-uppräkningen kedjas samman till en enda eskalering: instansroll → hanterad hemlig inventering. Steg 7 är en anspelning på capture-the-flag-inramningen — när en HTB{…} När flaggan hittas skickas den separat, annars delas den råa insamlade blobben upp i fyra delar och skickas.
Senare versioner i klustret tar eskaleringen vidare. Istället för att stanna vid en inventering analyserar de IMDS-svaret och exporterar de temporära nycklarna som AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_SESSION_TOKEN miljövariabler, bekräfta identiteten med aws sts få-ringer-identitet, och sedan loopa över varje hemlighet som returneras av list-hemligheter anropande aws secretsmanager get-secret-value på varje — hämtar det hemliga innehållet, inte bara deras namn. Samma versioner läser även processsubstituerade flaggbinärfiler (/läsflagga och vänner) och försöka lastkörning mot alla Rust-projekt som hittats under / app, vilket utvidgar skörden bortom molnuppgifter till vad byggmiljön än exponerar.
Dessa senare versioner använder sig också av mer aggressiva gateways. Utöver 12-hex-hostname och /app/node_modules kontrollerar nyttolasten den aktiva paketregisterkonfigurationen och arbetskatalogens sökväg och avslutas tyst när de indikerar en analys- eller speglingskontext snarare än ett aktivt mål. Den kombinerade effekten är en nyttolast som inte gör något observerbart i de flesta inspektionsmiljöer och kör sin fullständiga samling endast där den bedömer sig själv vara på en genuin containeriserad värd.
exfiltrationDen insamlade datan lämnar värden på två kanaler. Först en beacon POST till en fast webhook.site samlare, som innehåller värdnamn, numeriskt UID, arbetskatalog och upp till 120 KB insamlad data. För det andra viks informationen in i ett falskt YAML-"modulmanifest" och SÄTTA till /api/moduler/ på en målserver:
ecto_module: name: "<flag-or-chunk-0>" version: "1.0.0" power_level: "<chunk-1>" ship_deck: "<chunk-2>" cargo_hold: "<chunk-3>" Namnen på manifestfälten (effektnivå, skeppsdäck, lastrum) är dekoration — den stulna datan sitter inuti strängvärdena, vilket är anledningen till att en nätverksmonitor ser vad som ser ut som en godartad uppladdning av paketregistermanifest snarare än en uppenbar datadump. Beacon-kanalen bär mer: POST kropp till webhook.site inkluderar värdnamn, numeriskt UID, arbetskatalog och upp till 120 KB av den insamlade blobben, så att även en enda lyckad beacon levererar hela tagningen. webhook.site är en kostnadsfri tjänst för inspektion av förfrågningar; att använda den som en insamlare innebär att operatören aldrig behöver ställa upp en egen mottagningsinfrastruktur för den kanalen, och de inspelade förfrågningarna finns kvar i tjänstens korg.
Manifestet SÄTTA använder en reservlista som börjar med flera 127.0.0.1/lokalvärd portar och går sedan vidare till tre offentliga adresser i `154.57.164.0/24' intervall, och stannar vid den första slutpunkten som svarar med statusen 2xx. Ordningen "localhost-first" överensstämmer med självbeskrivningen "verdaccio testing" (ett lokalt register för loopback), men reservalternativen för public IP innebär att data lämnar värden när loopback inte lyssnar – det vill säga på vilken maskin som helst som inte är författarens egen testrigg.
tidslinje
Klustret visar stegvis kapacitetstillväxt snarare än en enda droppe. Vi ordnar det efter observerat beteende, inte efter publicerad väggklocka:
| Etapp | Paket / Versioner | Beteende |
|---|---|---|
| Tidig körning | coral-wraith 9999.0.x | Uppblåsta versionsnummer som överensstämmer med ett försök till beroendeförväxling; uppräkning vid installationstid och exfil |
| Seed | coral-wraith 1.0.0 | postinstall samlar in id/env/flag-filer; en enda PUT till 154[.]57[.]164[.]71:30782, markör ECT-472839 |
| Snabb iteration | coral-wraith 1.0.1 → 6.0.0 | Dussintals utsläpp på timmar; nyttolasten ökar isAppWorker() grind, IMDSv2-autentiseringsuppgifter, hela get-secret-value pivot, kontroll av register-/sökvägsmiljö och markörer för dubbla mottagare |
| Parallella namn | ecto-corsair-whisper-6f3b9 1.0.14–1.0.18 | Samma gated nyttolast; webhook.site beacon och reservlista för flera slutpunkter |
| varianter | ecto-rust-read-f3a9c1 1.0.1–1.0.2 | Lägger till extra markörer för diskbänken ECT-987654, ECT-654321, ECT-839201 |
| varianter | ecto-corsair-flag-x9m4 1.0.0, ecto-nightly-spirit 1.1.0 | Samma gated nyttolast, samma C2 och beacon |
Det som utmärker klustret är dess publiceringskadens: snarare än ett paket och en version publiceras samma namn om och om igen i snabb följd, varje utgåva har en liten variant av den förra, tillsammans med en handfull syskon med olika namn som bär samma nyttolast. Inom viska familjen delades koden upp i två nära fingeravtryck — en uppsättning som utlöser två kritiska detektioner, en annan tre (en ytterligare filläsningsmottagare) — men båda löser upp till samma nyttolast; skillnaden är koddrift, inte en beteendeförgrening. Versioner av viska bortom det analyserade intervallet (upp till minst 1.0.25 i skrivande stund) observerades live i registret, och korallvålnad namnet fortsatte att klättra på sin egen versionsstege över samma fönster.
Indikatorer för kompromiss
Alla indikatorer nedan extraherades från paketets källkod på disken. Nätverksindikatorerna är defangerade.
nätverks
| Indikator | Roll |
|---|---|
hxxp://154[.]57[.]164[.]71:30782 | C2 PUT-mål (coral-wraith) |
hxxp://154[.]57[.]164[.]80:30543 | C2 PUT-reserv (ecto-*) |
hxxp://154[.]57[.]164[.]82:31250 | C2 PUT-reserv (ecto-*) |
hxxp://154[.]57[.]164[.]71:31289 | C2 PUT-reserv (ecto-*) |
hxxps://webhook[.]site/602a4c72-7033-4e28-92ea-dc66e59206e5 | Beacon-samlare |
169[.]254[.]169[.]254/latest/... | IMDSv2-autentiseringsuppgifter lästa (målsidan, AWS-metadata) |
Beteende/fil
| Indikator | Roll |
|---|---|
"postinstall": "node postinstall.js" | Installera vektor |
ecto_module: YAML med power_level / ship_deck / cargo_hold nycklar | Exfil manifestschema |
Diskbänksmarkörer ECT-472839, ECT-987654, ECT-654321, ECT-839201 | C2-vägsegment /api/modules/<marker> |
isAppWorker() grind: värd /^[0-9a-f]{12}$/, cwd innehåller /app/node_modules | Aktiveringsvillkor |
aws secretsmanager list-secrets över us-east-1, eu-west-1, eu-central-1 | Uppräkning av hemligheter |
HTB{...} regex-skrapning | Fånga flaggan-skörden |
Filhash (sha256, insamlad vid analys)
| Fil | sha256 |
|---|---|
coral-wraith/postinstall.js | ce5ff035cfdfed1d0015446424b352c27b66bcb77e9fdb0a51e4245199146824 |
ecto-corsair-whisper-6f3b9 1.0.18/postinstall.js | b58432acba376aa6976f0490d9a1c04257ccdbc856d8390260c50322d63e31c3 |
Attribuering och observerat beteende
De fem paketnamnen publicerades under två npm-kontonamn, men de delar tillräckligt med infrastruktur för att behandla dem som ett kluster: samma ecto_module manifest schema, samma ECT-472839 primär diskbänksmarkör, samma webhook.site samlar-ID och C2-slutpunkter i samma 154.57.164.0/24`-blocket. Fröpaketet (`coral-wraith`), enklare och ogated) och den gated ekto-familjen läses således som iterationer på en verktygslåda snarare än oberoende ansträngningar.
Paketen beskriver sig själva, i senare versioner, som "CTF-nyttolast för Verdaccio-leveranskedjans testning." Vi lyfter fram den etiketten som ett observerbart faktum och upprepar den inte som ett resultat om syfte. Vad koden gör är entydigt och oberoende av hur den är etiketterad: den läser IAM-rollautentiseringsuppgifter från instansens metadatatjänst, räknar upp hanterade hemligheter över tre AWS-regioner och överför resultaten till en offentlig IP-adress och en tredjeparts webhook-insamlare. En genuint loopback-only testharness skulle inte behöva reservlistan för offentliga IP-adresser, IMDS-autentiseringsuppgifternas läsningar eller anropen för Secrets Manager mellan regioner. Eftersom utgående och autentiseringsuppgifternas räckvidd är verkliga klassificerades de gatade versionerna som skadliga.
Container-only-gate är den mest operativt anmärkningsvärda egenskapen. Det är både en undvikningsåtgärd – att vara tyst på bärbara datorer och i analys-sandlådor – och en målinriktad åtgärd, som bara utlöser där en verklig IAM-roll och live-hemligheter är mest sannolikt att finnas. Analytiker som kör dessa paket i en generisk sandlåda skulle inte observera någonting; beteendet manifesterar sig bara under ett Docker-liknande värdnamn och en installationssökväg i containern.
Påverkan, trender och vägledning för försvarare
Exponeringen här är molnuppgifter och hemliga avslöjanden i bygg- och runtime-containrar. En IAM-rolluppgifter som hämtas från IMDS innehåller de behörigheter som den rollen har; secretsmanager: ListSecrets (och eventuella efterföljande GetSecretValue) utvidgar det till lagrade applikationshemligheter. Miljövariabeldumpar innehåller ofta registertokens, databas-URL:er och API-nycklar. I ett CI- eller containerkontext – precis vad gate-systemet väljer – räcker en enda transitiv installation av ett av dessa paket för att läcka det materialet.
Ektoplasma passar in i ett mönster vi fortsätter att se: nyttolaster vid installationstid som använder molnmetadata och hanterade hemligheter snarare än lokala filer, och som endast aktiverar aktivering i miljöer med högt värde. Två defensiva observationer följer.
- Formen är detekterbarEn npm/PyPI-installationskrok vars anropsgraf når både ett molnhemlighets-API (aws hemlighetshanterare, gcloud-hemligheter, AZ KeyVault) eller IMDS-adressen och en nätverksutgående mottagare är ett smalt mönster med hög signal — det förekommer nästan aldrig i ett legitimt livscykelskript. Statisk flödesanalys kan flagga den utan att vara beroende av någon specifik domän eller IP-adress.
- Miljöhärdning avtrubbar den. Att tillämpa IMDSv2 med en hoppgräns på 1 förhindrar att containerarbetsbelastningar når instansmetadata; att begränsa IAM-roller till minsta möjliga behörighet begränsar läckageradien för alla autentiseringsuppgifter som läcker; och att köra installationer med --ignorera-skript I CI tas install-hook-vektorn bort helt för paket som inte behöver den.
För försvarare är de praktiska kontrollerna: varning om utgående anslutningar från build/CI-behållare till icke-tillåtna offentliga IP-adresser under npm installera; var uppmärksam på IMDS-åtkomst som kommer från paketlivscykelskript; och behandla alla installationshooks som skickas ut till ett moln-CLI som misstänkta tills motsatsen bevisats.
Två ytterligare anmärkningar specifika för detta kluster. För det första, eftersom aktivering är gated till containermiljöer, kan ett paket som verkar inert när det granskas på en arbetsstation fortfarande vara live i produktion – granskningen måste reproducera containerns värdnamn och sökvägsvillkor, eller läsa källkoden direkt, snarare än att förlita sig på "Jag installerade det och ingenting hände". För det andra innebär användningen av en offentlig förfrågningsinspektionstjänst som en beacon-insamlare att en del av den exfiltrerade datan kan återställas för incidenthantering: en organisation som hittar ett av dessa paket i sitt beroendeträd kan resonera om vad en lyckad beacon skulle ha innehållit utifrån nyttolastens insamlingslogik, och bör rotera alla IAM-rolluppgifter, registertokens och hanterade hemligheter som var nåbara från den berörda bygg- eller körmiljön. Rotation av behörighetsuppgifter, inte borttagning av paket, är den operativa åtgärden när en installation har körts.





