TL; DR
Клъстер от пет npm пакета, публикувано в два профила, изпратено postinstall кука, която чете облачни идентификационни данни от хоста и ги изпраща директно. Пакетите носят фалшиви имена — coral-wraith, ecto-corsair-whisper-6f3b9, ecto-corsair-flag-x9m4, ecto-rust-read-f3a9c1, ecto-nightly-spirit — и сериализират каквото съберат, в фалшив ecto_module: YAML манифеста преди предаването му. Проследяваме клъстера като ектоплазма.
Полезният товар се изпълнява само когато открие специфична среда: хост, чието име е 12-символен шестнадесетичен низ и работна директория под /app/node_modules — формата на контейнеризирана сборка или CI worker. Когато този гейт премине, куката запитва Услуга за метаданни за екземпляри на AWS (IMDSv2) за идентификационни данни за IAM роли, изброява AWS Secrets Manager в три региона, извлича променливи на средата, чете файлове под /appи търси низове за заснемане на флага. След това извлича резултата по два начина: маяк към webhook.site колектор и манифест PUT към крайна точка със суров IP адрес, със списък с резервни опции, първоначално използвани от localhost.
По-късните описания на пакетите гласят „CTF полезен товар за тестване на веригата за доставки на Verdaccio“. Докладваме това самоописание като наблюдаем факт. Самото поведение – изход в реално време към публичен IP адрес, четене на истински IMDS идентификационни данни, истински повиквания към Secrets Manager – е това, което е, независимо от етикета, и е причината тези версии да бъдат класифицирани като злонамерени.
Едно име в клъстера, coral-wraith, не спря с едно единствено издание. Преиздаде се бързо в десетки версии само за часове — 1.0.0 изкачване до 6.0.0 — и по-ранен цикъл със същото име е използвал напомпани 9999.0.x номера на версиите, класическата форма на опит за объркване на зависимостиПрез този период на нестабилност полезният товар видимо се разви: от еднократен маяк за изброяване на хостове до пълноценен AWS идентификационен център, обвит в проверки на средата, които го предпазват от движение извън предвидената цел.
| Услуги | 5 имена; coral-wraith сам преиздаден в десетки версии |
| Екосистема | NPM |
| Инсталиране на вектор | postinstall скрипт за жизнен цикъл |
| Основна цел | Идентификационни данни за ролята на AWS IAM + секретни стойности на Secrets Manager, променливи на средата, /app файлове |
| Изгонване | webhook.site маяк + суров IP адрес C2 PUT |
| Тригерен порт | 12-шестнадесетично име на хост + /app/node_modules cwd, плюс проверка на средата, която потиска полезния товар извън този контекст |
| Суровост | Високо — разкриване на облачни идентификационни данни и управлявана тайна от контейнеризирани среди за изграждане и изпълнение |
Анатомия на атаката
Всеки пакет в клъстера е изграден по един и същи начин: почти празен index.js (модул.експорт = {}), едноредов package.json сценарий — „postinstall“: „възел postinstall.js“ — и полезния товар в postinstall.jsИнсталирането на пакета е достатъчно, за да се изпълни hook-ът; не е необходим импорт или извикване.
Порталът за насочване. Преди да направи каквото и да е, полезният товар на ектосемейството проверява обкръжението си:
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 12-шестнадесетично име на хост е формата по подразбиране, която Docker присвоява на контейнер, и /app/node_modules е конвенционален път за инсталиране в контейнер. Третата клауза се отказва, ако пътят изглежда като директория за извличане в пясъчник. Крайният ефект е, че полезният товар остава спящ на лаптоп на разработчика или в пясъчник за анализ и се активира само в контейнеризирана среда за изграждане или изпълнение - видът среда, която е най-вероятно да съдържа активни облачни идентификационни данни. Най-ранният пакет в клъстера, коралов призрак, няма такъв порт и изпълнява своята (по-проста) колекция безусловно.
колекцияКогато портата минава, куката се изплъзва навън. execFileSync(„/bin/sh“, [„-c“, …]) и изпълнява една съставна команда, която, в следния ред:
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) Стъпки 1–3 са учебникарско извличане на IMDSv2: заявете токен за сесия, след което го прикачете като X-aws-ec2-metadata-token заглавката за извличане на IAM ролята на екземпляра и временните ключове за достъп на тази роля. Изборът за внедряване на IMDSv2, вместо по-простия неавтентичен IMDSv1 GET заслужава да се отбележи — това означава, че полезният товар работи дори на инстанции, конфигурирани да изискват достъп до метаданни, базиран на токени, което е препоръчителното от AWS укрепване. Идентификационните данни, върнати от стъпка 3, са краткотрайни. Идентификатор на ключ за достъп/ТаенКлючЗаДостъп/Жетон тройки, обхванати от ролята на инстанцията; каквото и да може да прави тази роля, притежателят на тези ключове може да го прави през целия живот на идентификационните данни.
Стъпки 4–6 разширяват обхвата. Околна среда dump записва всичко, което процесът на изграждане или изпълнение е наследил — на практика това е мястото, където най-често се намират токени на системния регистър, низове за свързване с база данни и API ключове. / ап file walk чете до петнадесет файла на приложението извън модули_възел, която може да повърхностна конфигурация, .env файлове или източник. Стъпка 6 извиква AWS SecretsManager списък-секрети в три региона; идентификационните данни, извлечени в стъпки 1–3, са точно това, което удостоверява тези повиквания, така че четенето на IMDS и изброяването на Secrets Manager се свързват в една ескалация: роля на екземпляр → инвентаризация на управлявана тайна. Стъпка 7 е намигване към рамкирането на capture-the-flag — когато ХТБ{…} Ако се намери флаг, той се изпраща самостоятелно, в противен случай суровият събран блоб се разделя на четири части и се изпраща.
По-късните версии в клъстера продължават ескалацията. Вместо да спират на инвентаризация, те анализират IMDS отговора, експортират временните ключове като AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_SESSION_TOKEN променливи на средата, потвърдете идентичността с AWS STS получаване на самоличност на повикващияи след това да преглежда всяка тайна, върната от списък-тайни повикване aws secretsmanager get-secret-value на всяка от тях — извличане на тайното съдържание, не само на имената им. Същите версии четат и двоични файлове с флагове, заместени от процесите (/readflag и приятели) и опитайте зарядно бягане срещу всеки Rust проект, намерен под / ап, разширявайки събирането на данни отвъд облачните идентификационни данни към всичко, което средата за изграждане излага.
Тези по-късни версии също така се самозащитават по-агресивно. В допълнение към 12-хестнадесетичното име на хост и /app/node_modules При проверките, полезният товар проверява активната конфигурация на системния регистър на пакетите и пътя до работната директория и излиза безшумно, когато те показват контекст за анализ или огледало, а не активна цел. Комбинираният ефект е полезен товар, който не прави нищо наблюдаемо в повечето среди за проверка и изпълнява пълната си колекция само там, където прецени, че е на истински контейнеризиран хост.
идващаСъбраните данни напускат хоста по два канала. Първо, маяк ПУСНИ към фиксиран webhook.site колектор, носещ името на хоста, числовия UID, работната директория и до 120 KB събрани данни. Второ, данните се сгъват във фалшив YAML „манифест на модула“ и PUT да се /api/модули/ на целевия сървър:
ecto_module: name: "<flag-or-chunk-0>" version: "1.0.0" power_level: "<chunk-1>" ship_deck: "<chunk-2>" cargo_hold: "<chunk-3>" Имената на полетата на манифеста (ниво_на_мощност, корабна_палуба, товарен отсек) са декорация — откраднатите данни се намират вътре в низовите стойности, поради което мрежовият монитор вижда нещо, което изглежда като безобидно качване на манифест от системния регистър на пакети, а не очевидно дъмп на данни. Каналът на маяка носи повече: ПУСНИ тяло към webhook.site включва името на хоста, числовия UID, работната директория и до 120 KB от събрания blob, така че дори един успешен маяк предоставя пълния обем. webhook.site е безплатна услуга за проверка на заявки; използването ѝ като събирач означава, че операторът никога не е необходимо да изгражда собствена инфраструктура за получаване на този канал, а записаните заявки се съхраняват в кошчето на услугата.
Манифестът PUT разглежда резервен списък, който започва с няколко 127.0.0.1/Localhost портове и след това попада към три публични адреса в `154.57.164.0/24` диапазон, спирайки на първата крайна точка, която отговаря със статус 2xx. Подреждането localhost-first е в съответствие със самоописанието „verdaccio testing“ (локален регистър на loopback), но резервните public-IP адреси означават, че данните напускат хоста, когато loopback не слуша - тоест, на всяка машина, която не е собствената тестова платформа на автора.
История
Клъстерът показва постепенен растеж на капацитета, а не единичен спад. Подреждаме го по наблюдавано поведение, а не по време на публикуване:
| Етап | Пакети / Версии | Поведение |
|---|---|---|
| Ранно бягане | coral-wraith 9999.0.x | Завишени номера на версиите, съответстващи на опит за объркване на зависимости; изброяване по време на инсталиране и изключване |
| семена | coral-wraith 1.0.0 | след инсталация се събират файлове с id/env/flag; единичен PUT към 154[.]57[.]164[.]71:30782, маркер ECT-472839 |
| Бърза итерация | coral-wraith 1.0.1 → 6.0.0 | Десетки изпускания за часове; полезният товар набира isAppWorker() порта, изтегляне на IMDSv2 идентификационни данни, пълното get-secret-value Pivot, проверката на средата на системния регистър/пътя и маркерите за двоен приемник |
| Паралелни имена | ecto-corsair-whisper-6f3b9 1.0.14–1.0.18 | Същият полезен товар с ограничен достъп; webhook.site списък с маяци и резервни опции за множество крайни точки |
| Варианти | ecto-rust-read-f3a9c1 1.0.1–1.0.2 | Добавя допълнителни маркери за мивка ECT-987654, ECT-654321, ECT-839201 |
| Варианти | ecto-corsair-flag-x9m4 1.0.0, ecto-nightly-spirit 1.1.0 | Същият полезен товар с ограничен достъп, същият C2 и маяк |
Определящата черта на клъстера е неговата честота на публикуване: вместо един пакет и една версия, едно и също име се преиздава отново и отново в бърза последователност, като всяко издание е малка вариация на предишното, наред с шепа различно наименувани братя и сестри, носещи един и същ полезен товар. В рамките на шепот В семейството кодът се разделя на два близки пръстови отпечатъка — единият набор задейства две критични детекции, другият три (допълнителен приемник за четене на файл) — но и двата се равняват на един и същ полезен товар; разликата е дрейф на кода, а не поведенческо разклонение. Версии на шепот извън анализирания диапазон (до поне 1.0.25 към момента на писане) са наблюдавани на живо в регистъра и коралов призрак Името продължи да се изкачва по собствената си стълбица от версии през същия прозорец.
Индикатори за компромис
Всички индикатори по-долу са извлечени от изходния код на пакета на диска. Мрежовите индикатори са деинсталирани.
мрежа
| индикатор | Роля |
|---|---|
hxxp://154[.]57[.]164[.]71:30782 | C2 PUT цел (coral-wraith) |
hxxp://154[.]57[.]164[.]80:30543 | C2 PUT резервен вариант (ecto-*) |
hxxp://154[.]57[.]164[.]82:31250 | C2 PUT резервен вариант (ecto-*) |
hxxp://154[.]57[.]164[.]71:31289 | C2 PUT резервен вариант (ecto-*) |
hxxps://webhook[.]site/602a4c72-7033-4e28-92ea-dc66e59206e5 | Колекционер на маяци |
169[.]254[.]169[.]254/latest/... | Четене на IMDSv2 идентификационни данни (от страната на целта, метаданни на AWS) |
Поведенчески/файлови
| индикатор | Роля |
|---|---|
"postinstall": "node postinstall.js" | Инсталиране на вектор |
ecto_module: YAML с power_level / ship_deck / cargo_hold ключове | Схема на манифеста на Exfil |
Маркери за мивки ECT-472839, ECT-987654, ECT-654321, ECT-839201 | C2 сегмент от пътя /api/modules/<marker> |
isAppWorker() порта: хост /^[0-9a-f]{12}$/, cwd съдържа /app/node_modules | Условие за активиране |
aws secretsmanager list-secrets над us-east-1, eu-west-1, eu-central-1 | Изброяване на тайни |
HTB{...} изстъргване на регулярни изрази | Жътва тип „превземи знамето“ |
Хешове на файлове (sha256, заснети по време на анализа)
| досие | sha256 |
|---|---|
coral-wraith/postinstall.js | ce5ff035cfdfed1d0015446424b352c27b66bcb77e9fdb0a51e4245199146824 |
ecto-corsair-whisper-6f3b9 1.0.18/postinstall.js | b58432acba376aa6976f0490d9a1c04257ccdbc856d8390260c50322d63e31c3 |
Атрибуция и наблюдавано поведение
Петте имена на пакети бяха публикувани под два npm акаунта, но те споделят достатъчно инфраструктура, за да се третират като един клъстер: същото ектомодул схема на манифеста, същата ECT-472839 основен маркер за мивка, същият webhook.site идентификатор на колектора и крайни точки на C2 в едно и също Блок 154.57.164.0/24`. Пакетът със семена (`coral-wraith, по-прости и незатворени) и затвореното екто-семейство по този начин се четат като итерации на един инструментариум, а не като независими усилия.
Пакетите се описват, в по-късни версии, като „Полезен товар на CTF за тестване на веригата за доставки на Verdaccio.“ Ние представяме този етикет като наблюдаем факт и не го преформулираме като констатация за целта. Това, което прави кодът, е недвусмислено и независимо от това как е обозначен: той чете идентификационните данни за IAM роли от услугата за метаданни на екземпляра, изброява управляваните тайни в три AWS региона и предава резултатите към публичен IP адрес и колектор на webhook на трета страна. Тестовият хайнинг, който наистина е само с обратна връзка, не би се нуждаел от резервния списък с публични IP адреси, четенето на IMDS идентификационни данни или извикванията на Secrets Manager между регионите. Тъй като изходният достъп и обхватът на идентификационните данни са реални, затворените версии бяха класифицирани като злонамерени.
„Само контейнерният порт“ е най-забележителната от оперативна гледна точка характеристика. Той е едновременно мярка за избягване – запазване на тишина на лаптопи и в пясъчник за анализ – и мярка за насочване, като се задейства само там, където е най-вероятно да има истинска IAM роля и активни секрети. Анализаторите, които изпълняват тези пакети в общ пясъчник, не биха забелязали нищо; поведението се проявява само под име на хост в стил Docker и път за инсталиране в контейнера.
Въздействие, тенденции и насоки за защитниците
Разкриването на идентификационни данни за облака и тайни данни в контейнери за изграждане и изпълнение. Идентификационните данни за IAM роля, извлечени от IMDS, носят всички разрешения, които тази роля притежава; secretsmanager:СписъкСекрети (и всяко последващо GetSecretValue) разширява това до съхранени тайни на приложенията. Дъмповете на променливите на средата често съдържат токени на системния регистър, URL адреси на бази данни и API ключове. В контекст на CI или контейнер - точно това, което шлюзът избира - една транзитивна инсталация на един от тези пакети е достатъчна, за да изтече този материал.
Ectoplasm се вписва в модел, който продължаваме да наблюдаваме: полезни товари по време на инсталиране, които достигат до облачни метаданни и управлявани тайни, а не до локални файлове, и които се задействат само във висококачествени среди. Следват две защитни наблюдения.
- Формата е разпознаваема. Инсталационен кук на npm/PyPI, чийто граф на извикванията достига както до API на облачни тайни (AWS SecretsManager, тайните на gcloud, AZ хранилище за ключове) или IMDS адресът и изходният мрежов приемник е тесен модел с висок сигнал — той почти никога не се среща в легитимен скрипт за жизнения цикъл. Статичен анализ на потока може да го маркира, без да зависи от конкретен домейн или IP адрес.
- Втвърдяването на околната среда го притъпява. Прилагането на IMDSv2 с ограничение на прехода от 1 предотвратява достигането на метаданните на инстанциите от работните натоварвания на контейнерите; ограничаването на обхвата на IAM ролите до най-малки привилегии ограничава радиуса на изтичане на всички идентификационни данни, които изтекат; и стартирането на инсталации с –Игнориране на скриптове В CI премахва изцяло вектора install-hook за пакети, които не се нуждаят от него.
За защитниците, практическите проверки са: предупреждение за изходящи връзки от контейнери за изграждане/CI към публични IP адреси, които не са в разрешения списък, по време на npm инсталиране; следете за IMDS достъп, произхождащ от скриптове за жизнения цикъл на пакета; и третирайте всяка инсталационна кука, която се насочва към облачен CLI, като подозрителна, докато не се докаже противното.
Две допълнителни бележки, специфични за този клъстер. Първо, тъй като активирането е ограничено до контейнеризирани среди, пакет, който изглежда инертен при проверка на работна станция, все още може да бъде активен в продукция — проверката трябва да възпроизведе името на хоста на контейнера и условията на пътя или да прочете директно изходния код, вместо да разчита на „Инсталирах го и нищо не се случи“. Второ, използването на публична услуга за проверка на заявки като колектор на маяци означава, че някои от извлечените данни може да бъдат възстановими за реагиране при инциденти: организация, която открие един от тези пакети в своето дърво на зависимости, може да разсъждава за това какво би съдържал успешен маяк от логиката на събиране на полезния товар и трябва да завърти всички идентификационни данни за IAM роля, токени в системния регистър и управлявани тайни, които са били достъпни от засегнатата среда за изграждане или изпълнение. Ротация на пълномощията, а не премахването на пакета, е оперативното отстраняване след изпълнение на инсталацията.





