Вредоносный червь Miasma поразил 20 пакетов экосистемы LeoPlatform в npm после компрометации одного аккаунта мейнтейнера

Атака на цепочку поставок NPM

24 июня 2026 года в 23:04:55 UTC в реестре npm произошла атака на цепочку поставок, затронувшая 20 пакетов, связанных с организацией LeoPlatform. Злоумышленник получил доступ к учётной записи разработчика Клинта Циркера (czirker@commercehub.com) и использовал его токены для npm и GitHub, чтобы за три секунды опубликовать заражённые версии всех 20 пакетов. Суммарный еженедельный объём загрузок этих модулей составляет около 13 600. Инцидент представляет серьёзную угрозу для всех проектов, использующих данные зависимости, особенно для организаций, которые применяют LeoPlatform в своей инфраструктуре.

Описание

Каждая заражённая версия получила три одинаковых изменения по сравнению с предыдущей чистой сборкой. Первое - новый файл binding.gyp, содержащий единственную цель для node-gyp. При запуске npm install система автоматически вызывает node-gyp rebuild, если в пакете присутствует binding.gyp. Внутри файла используется синтаксис GYP command expansion для выполнения произвольной команды: "<!(node index.js > /dev/null 2>&1 && echo stub.c)". Этот механизм запускает вредоносный код index.js в момент установки пакета, минуя проверки, которые отслеживают только стандартные скрипты жизненного цикла (preinstall, postinstall). Второе изменение - оригинальный модульный код index.js полностью заменён односложной обфусцированной строкой размером около 5,2 МБ. Обфускация состоит из трёх слоёв: для каждого пакета применяются разные значения шифра ROT (5, 8, 19 или 23) и различные ключи AES-128-GCM. После расшифровки получается два одинаковых для всех пакетов двоичных блоба: загрузчик среды выполнения Bun (907 байт) и непосредственно червь (781 580 байт). Третье изменение - в каждый package.json добавлена зависимость ""bun": "^1.3.13"", которая служит запасным путём доставки рантайма, если загрузчику не удастся скачать Bun напрямую.

Загрузчик (_b) обращается к GitHub Releases по адресу github.com/oven-sh/bun/releases/download/bun-v1.3.13/, кэширует исполняемый файл в временной директории и предоставляет глобальную функцию getBunPath(). Затем червь (_p) записывается в файл /tmp/p<случайное>.js и запускается через bun run, после чего временный файл удаляется. Такой подход позволяет атаке работать на любой системе, где есть curl или wget, а наличие прямого npm-пакета bun гарантирует выполнение даже в изолированных окружениях.

Главной причиной атаки стала компрометация единственного аккаунта мейнтейнера czirker, который присутствует во всех 20 заражённых пакетах. Другие мейнтейнеры (leoinights, jgrantr, elsmob) присутствуют лишь в части пакетов, но общим знаменателем является именно учётная запись Циркера. Злоумышленник использовал его npm-токен для массовой публикации и GitHub-токен для атак на репозитории. Предоставленный анализ метаданных реестра показывает, что между опубликованными версиями проходил значительный временной разрыв: например, пакет rstreams-shard-util перешёл с версии 1.0.0 (ноябрь 2024) на заражённую 1.0.1 (24 июня 2026). Четыре пакета того же разработчика остались нетронутыми - leo-connector-common, leo-connector-entity-table, leo-connector-postgres и leo-connector-sqlserver. Все они имеют тег latest, указывающий на предрелизную версию (-rc или -beta). Вероятно, червь пропускает пакеты, у которых последняя помеченная версия не является стабильной.

Помимо npm, атака распространилась и на GitHub. Журналы событий для трёх репозиториев LeoPlatform (Nodejs, auth-sdk, Leo) показывают, что примерно за 14 минут до публикации в npm аккаунт czirker создал orphan-ветки с именами snapshot-<8_шестнадцатеричных_символов>. В этих ветках были размещены два файла: вредоносный скрипт _index.js (те же 5,2 МБ) и файл workflow .github/workflows/npm-publish.yml, замаскированный под "Dependabot Updates". Workflow запускается при любом push в любую ветку, запрашивает разрешение id-token: write (что даёт доступ к OIDC-токену GitHub, который может быть обменян на учётные данные npm через доверенную публикацию) и выполняет команду bun run _index.js с переменными окружения, содержащими имя пакета и идентификатор репозитория. Дополнительный коммит от имени dependabot[bot] заменяет параметры OIDC на прямое указание секрета NPM_TOKEN, что свидетельствует о попытке применить несколько стратегий публикации. Основная ветка master остаётся чистой; заражённый workflow существует только на orphan-ветке, но он выполнится, если CI настроен на запуск из всех веток или если кто-то сольёт эту ветку.

Внутренняя структура червя использует стандартный паттерн обфускации javascript-obfuscator: таблица строк _0x66ee с 2 588 записями, декодер _0x42e6 и вторичный декодер, собранный во время выполнения, который вызывается 519 раз для расшифровки имён переменных окружения и конечных точек API. Статический анализ расшифрованной полезной нагрузки подтверждает полный набор возможностей, описанный ранее для семейства Miasma. Это кража учётных данных из множества источников: npm, GitHub (PAT, OIDC, JWT), PyPI, RubyGems, токенов сервисных аккаунтов Kubernetes, HashiCorp Vault, AWS (ключи IAM, STS, IMDS, Secrets Manager, SSM), 1Password, JFrog Artifactory и SSH-ключей. Для поиска секретов используются регулярные выражения, нацеленные на auth-токены, приватные ключи и строки .npmrc. Особый признак Miasma - нацеливание на конфигурации AI-инструментов: в коде найдены ссылки на claudeSettingsPath, cursorRulesPath, geminiSettingsPath и vscodeTasksPath.

Последствия инцидента выходят далеко за рамки экосистемы LeoPlatform. Червь способен к самовоспроизведению через npm: он перечисляет репозитории пользователя, создаёт новые версии пакетов и пытается публиковать их через скомпрометированные токены или OIDC-учётные данные. Организации, использующие любой из 20 заражённых пакетов, должны рассматривать свои системы как потенциально скомпрометированные. Высока вероятность кражи облачных ключей, токенов CI/CD и доступа к инфраструктуре. Особое внимание стоит уделить проектам, которые применяют npm-пакеты в production-среде, а также системам с GitHub Actions, где могли быть активированы OIDC-потоки.

Диагностика заражения проста: любой пакет, который в недавнем обновлении версии получил файл binding.gyp со строкой "<!(node index.js", новую зависимость bun и index.js, выросший до нескольких мегабайт, следует считать вредоносным. Для оперативного выявления можно использовать специализированные утилиты, такие как PMG (Package Manager Guard), которая перехватывает установку и сверяется с фидом угроз, а также изолирует скрипты в песочнице. Рекомендуется немедленно отозвать все токены доступа, связанные с аккаунтом czirker, проверить журналы GitHub Events на наличие подозрительных orphan-веток и изменить секреты, которые могли быть раскрыты.

Этот случай подтверждает устойчивую тенденцию роста атак на цепочку поставок, где компрометация одного разработчика способна распространить вредонос по десяткам пакетов и репозиториев. Использование нетрадиционных векторов выполнения - через GYP command expansion и скрытые рантаймы - усложняет обнаружение традиционными средствами. Защита требует многослойного подхода: строгая многофакторная аутентификация для аккаунтов мейнтейнеров, мониторинг подозрительных изменений в пакетах, автоматическое сканирование файлов binding.gyp и внезапно выросших index.js, а также ограничение прав на OIDC-токены в GitHub Actions.

Индикаторы компрометации

SHA1

  • 24a0d9e496ec07ca978fab602d5f5e0b39fa03a0
  • 5e75c14b8acd5752819ab7a10874ddd6389f5238
  • a8cb86b78ca56befe90dc466642cb04b98079909
  • e973173fb757d2dab9c6424b440dd9f7cbe4f14a

SHA256

  • 9f93d77d32833a515bc406c46da477142bb1ac2babeecb6aa42f98669a6db015
  • ceff7c51d70832c3ec8dd2744b606a23b3c924ef664ae23439b9b742ea154108

Комментарии: 0