TL, д-р
Кластер п'ять 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-воркера. Коли цей гейт проходить, хук запитує Служба метаданих екземплярів AWS (IMDSv2) для облікових даних ролі IAM перераховує Менеджер секретів AWS у трьох регіонах, створює дампи змінних середовища, читає файли під /appта збирає рядки для захоплення прапорців. Потім він витягує результат двома способами: маяк до webhook.site колектор та маніфест PUT до кінцевої точки з необробленою IP-адресою, зі списком резервних варіантів localhost-first.
У наступних описах пакетів зазначено «CTF-корисне навантаження для тестування ланцюжка поставок Verdaccio». Ми повідомляємо про цей самоопис як про спостережуваний факт. Сама поведінка — вихід на публічну IP-адресу в реальному часі, реальне зчитування облікових даних IMDS, реальні виклики Secrets Manager — є такою, якою вона є, незалежно від мітки, і саме тому ці версії були класифіковані як шкідливі.
Одне ім'я в кластері, coral-wraith, не зупинився на одному релізі. Він швидко перевидався в десятках версій за лічені години — 1.0.0 підйом до 6.0.0 — а в попередньому випуску з такою ж назвою використовувалися завищені 9999.0.x номери версій, класична форма спроба плутанини з залежностямиКрізь цей хаос корисне навантаження помітно дозріло: від одноразового маяка переліку хостів до повноцінного опорного елемента облікових даних AWS, обгорнутого перевірками середовища, які тримають його в тиші поза межами цільового призначення.
| Пакети | 5 імен; coral-wraith тільки перевиданий у десятках версій |
| Екосистема | нмм |
| Встановити вектор | postinstall сценарій життєвого циклу |
| Основна ціль | Облікові дані ролі AWS IAM + значення секретів менеджера секретів, змінні середовища /app файли |
| Вигнання | webhook.site маяк + необроблений IP-адреса C2 PUT |
| Тригерний затвор | 12-шістнадцяткове ім'я хоста + /app/node_modules cwd, а також перевірка середовища, яка пригнічує корисне навантаження поза цим контекстом |
| Строгість | висока — розкриття хмарних облікових даних та керованого секрету від контейнеризовані середовища збірки та виконання |
Анатомія атаки
Кожен пакет у кластері побудований однаково: майже порожній index.js (module.exports = {}), однорядковий package.json сценарій — «postinstall»: «вузол postinstall.js» — і корисне навантаження в postinstall.jsВстановлення пакета достатньо для запуску хука; імпорт чи виклик не потрібен.
Ворота прицілювання. Перш ніж щось зробити, корисне навантаження екто-сім'ї перевіряє своє оточення:
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 призначає контейнеру, і /додаток/модулі_вузлів — це звичайний шлях встановлення в контейнері. Третій пункт завершує роботу, якщо шлях виглядає як каталог видобування в ізольованому середовищі. Кінцевий ефект полягає в тому, що корисне навантаження залишається неактивним на ноутбуці розробника або в ізольованому середовищі аналізу та активується лише всередині контейнеризованого робочого середовища збірки або середовища виконання — такого середовища, яке, найімовірніше, містить активні хмарні облікові дані. Найдавніший пакет у кластері, кораловий привид, не має такого гейта та запускає свою (простішу) колекцію безумовно.
COLLECTIONКоли ворота проходять, гак вилітає назовні. 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 заголовок для отримання ролі IAM екземпляра та тимчасових ключів доступу цієї ролі. Вибір реалізації IMDSv2 замість простішого неавтентифікованого IMDSv1 GET Варто зазначити, що це означає, що корисне навантаження працює навіть на екземплярах, налаштованих на вимогу доступу до метаданих на основі токенів, що є рекомендованим AWS посиленням. Облікові дані, повернуті кроком 3, є короткочасними. Ідентифікатор ключа доступу/СекретнийКлючДоступу/Знак триплети, обмежені роллю екземпляра; все, що може робити ця роль, може робити власник цих ключів протягом терміну дії облікових даних.
Кроки 4–6 розширюють охоплення. env dump фіксує все, що успадкував процес збірки або виконання — на практиці саме тут найчастіше зберігаються токени реєстру, рядки підключення до бази даних та ключі API. / app file walk зчитує до п'ятнадцяти файлів застосунків за межами вузли_модулі, який може мати конфігурацію поверхні, .env файли або джерело. Виклики кроку 6 AWS SecretsManager: список-секретів у трьох регіонах; облікові дані, отримані на кроках 1–3, саме автентифікують ці виклики, тому зчитування IMDS та перерахування менеджера секретів об'єднуються в єдину ескалацію: роль екземпляра → інвентаризація керованого секрету. Крок 7 є натяком на фреймування захоплення прапора — коли ГТБ{…} Якщо знайдено прапорець , він надсилається окремо, інакше необроблений зібраний блоб розділяється на чотири частини та надсилається.
Пізніші версії в кластері здійснюють подальшу ескалацію. Замість того, щоб зупинятися на інвентаризації, вони аналізують відповідь IMDS, експортують тимчасові ключі як AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / МАРКЕН_СЕСІЇ_AWS змінні середовища, підтвердіть ідентичність з AWS STS отримання ідентифікації абонента, а потім перебирає кожен секрет, що повертається список-секретів покликання AWS SecretsManager: отримання секретного значення на кожному — отримання секретного вмісту, а не лише їхніх імен. Ті ж версії також читають бінарні файли прапорців із заміною процесу (/прапорець_читання та друзі) та спробуйте зарядний біг проти будь-якого проекту Rust, знайденого під / app, розширюючи доступ за межі хмарних облікових даних на все, що надає середовище збірки.
Ці пізніші версії також агресивніше блокують себе. Окрім 12-шістнадцяткового імені хоста та /додаток/модулі_вузлів Під час перевірок корисне навантаження перевіряє активну конфігурацію реєстру пакетів та шлях до робочого каталогу й тихо завершує роботу, коли вони вказують на контекст аналізу або дзеркала, а не на активну ціль. Комбінований ефект полягає в тому, що корисне навантаження не виконує жодних спостережуваних дій у більшості середовищ перевірки та запускає повну колекцію лише там, де воно оцінює себе як справжнього контейнерного хоста.
ексфільтрацііЗібрані дані залишають хост по двох каналах. По-перше, маяк POST до фіксованого webhook.site колектор, що містить ім'я хоста, числовий UID, робочий каталог та до 120 КБ зібраних даних. По-друге, дані упаковуються у підроблений 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>" Назви полів маніфесту (рівень_потужності, палуба_корабля, вантажний відсік) є декорацією — викрадені дані знаходяться всередині рядкових значень, тому мережевий монітор бачить те, що виглядає як доброякісний завантажений маніфест реєстру пакетів, а не очевидний дамп даних. Канал маяка містить більше: POST тіло до webhook.site включає ім'я хоста, числовий UID, робочий каталог та до 120 КБ зібраного блобу, тому навіть один успішний маяк надає повний аналіз. webhook.site – це безкоштовний сервіс перевірки запитів; його використання як збирача означає, що оператору ніколи не доводиться створювати власну інфраструктуру для цього каналу, а записані запити зберігаються в кошику сервісу.
Маніфест PUT проходить резервний список, який починається з кількох 127.0.0.1/локальний порти, а потім потрапляє на три публічні адреси в `154.57.164.0/24` діапазон, зупиняючись на першій кінцевій точці, яка відповідає зі статусом 2xx. Порядок "localhost-first" відповідає самоопису "verdaccio testing" (локальний реєстр на зворотному зв'язку), але резервні IP-адреси публічної IP-адреси означають, що дані залишають хост щоразу, коли зворотний зв'язок не прослуховує, тобто на будь-якій машині, яка не є власним тестовим стендом автора.
Хронологія
Кластер демонструє поступове зростання можливостей, а не одноразове падіння. Ми впорядковуємо його за спостережуваною поведінкою, а не за часом публікації:
| Стажування | Пакети / Версії | Поведінка |
|---|---|---|
| Ранній забіг | coral-wraith 9999.0.x | Завищені номери версій, що відповідають спробі плутанини із залежностями; перерахування під час встановлення та виключення |
| Насіння | coral-wraith 1.0.0 | postinstall збирає файли 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-адресу та сторонній колектор вебхуків. Тестовий хандінг, що справді працює лише з петлею, не потребуватиме резервного списку публічних IP-адрес, зчитування облікових даних IMDS або викликів менеджера секретів між регіонами. Оскільки вихідний доступ та охоплення облікових даних є реальними, закриті версії були класифіковані як шкідливі.
Шлюз, доступний лише для контейнерів, є найбільш помітною з операційної точки зору. Це одночасно захід уникнення — мовчання на ноутбуках та в аналітичних пісочницях — і захід цільової атаки, що спрацьовує лише там, де найімовірніше присутня реальна роль IAM та активні секрети. Аналітики, які запускають ці пакети в загальній пісочниці, нічого не помітять; така поведінка проявляється лише під іменем хоста в стилі Docker та шляхом встановлення в контейнері.
Вплив, тенденції та рекомендації для захисників
Розкриття тут полягає в розкритті хмарних облікових даних та секретних даних у контейнерах збірки та середовища виконання. Облікові дані ролі IAM, отримані з IMDS, містять усі дозволи, які має ця роль; secretsmanager:СписокСекретів (та будь-які подальші ОтриматиСекретнеЗначення) розширює це до збережених секретів програм. Дампи змінних середовища часто містять токени реєстру, URL-адреси баз даних та ключі API. У контексті неперервної інтеграції або контейнера — саме те, що вибирає шлюз — однієї транзитивної інсталяції одного з цих пакетів достатньо для витоку цього матеріалу.
Ectoplasm відповідає шаблону, який ми продовжуємо спостерігати: корисні навантаження під час встановлення, які звертаються до хмарних метаданих та керованих секретів, а не до локальних файлів, і які самі запускаються лише у високоцінних середовищах. Далі наведено два захисних спостереження.
- Форма виявляється. Хук встановлення npm/PyPI, граф викликів якого досягає як API хмарних секретів (менеджер секретів AWS, секрети gcloud, сховище ключів AZ) або адреса IMDS та мережевий вихідний стік – це вузький шаблон із високим рівнем сигналу — він майже ніколи не зустрічається в легітимному сценарії життєвого циклу. Статичний аналіз потоку можна позначити це незалежно від конкретного домену чи IP-адреси.
- Загартування навколишнього середовища притуплює його. Застосування IMDSv2 з обмеженням кількості переходів, рівним 1, запобігає досягненню робочих навантажень контейнерів метаданими екземпляра; обмеження ролей IAM до найменших привілеїв обмежує радіус витоку будь-яких облікових даних, що витікають; та запуск інсталяцій з –Ігнорувати сценарії У CI повністю видаляє вектор перехоплення установки для пакетів, яким він не потрібен.
Для захисників практичні перевірки такі: сповіщення про вихідні з’єднання від контейнерів збірки/CI до публічних IP-адрес, що не входять до білого списку, під час npm встановити; стежте за доступом до IMDS, що походить зі скриптів життєвого циклу пакета; та розглядайте будь-який перехоплювач встановлення, який спрямовує дії до хмарного CLI, як підозрілий, доки не буде доведено протилежне.
Ще два зауваження, характерні для цього кластера. По-перше, оскільки активація обмежена контейнерними середовищами, пакет, який виглядає інертним під час перевірки на робочій станції, все ще може бути активним у продакшені — перевірка повинна відтворювати ім'я хоста контейнера та умови шляху або безпосередньо зчитувати вихідний код, а не покладатися на принцип «Я встановив його, і нічого не сталося». По-друге, використання публічної служби перевірки запитів як збирача маяків означає, що деякі вилучені дані можуть бути відновлені для реагування на інциденти: організація, яка знаходить один із цих пакетів у своєму дереві залежностей, може міркувати про те, що містив би успішний маяк, виходячи з логіки збору корисного навантаження, і повинна ротувати будь-які облікові дані ролі IAM, токени реєстру та керовані секрети, які були доступні з ураженого середовища збірки або виконання. Ротація посвідчень, а не видалення пакета, є оперативним виправленням після запуску інсталяції.





