Червь Mini Shai-Hulud скомпрометировал npm-пакеты TanStack и другие проекты, используя подмену CI/CD

information security

Группа TeamPCP запустила новую волну самораспространяющегося червя Mini Shai-Hulud. Вредоносная программа атаковала механизмы непрерывной интеграции и доставки (CI/CD), чтобы украсть секреты и опубликовать заражённые версии популярных npm-пакетов. Наиболее пострадавшей экосистемой стал проект TanStack: 84 вредоносные версии были загружены через 42 пакета @tanstack/*, которые в сумме загружаются миллионы раз в неделю. Атака затронула также пакеты от UiPath, DraftLab, Mistral AI, OpenSearch и других разработчиков.

Описание

Инцидент примечателен тем, что червь впервые в истории создал вредоносные пакеты с действительной аттестацией происхождения SLSA уровня 3. Это означает, что подпись подтверждала легитимность сборки, хотя сама сборка была скомпрометирована. Злоумышленники использовали кражу OIDC-токенов (токенов открытого идентификатора, которые позволяют подтверждать личность без пароля) из памяти процесса Runner.Worker на GitHub Actions. После получения доступа к одному пайплайну червь перечислял все пакеты, принадлежащие тому же мейнтейнеру, и публиковал заражённые версии каждого из них.

Эксперты компании StepSecurity сообщили о детектировании атаки с помощью своего анализатора пакетов на основе искусственного интеллекта. Они уведомили мейнтейнеров скомпрометированных пакетов. TanStack выпустил подробный отчёт о постмортеме, признав, что атака стала возможной из-за цепочки трёх уязвимостей: вредоносный запрос pull_request_target, отравление кэша GitHub Actions и кража OIDC-токена из памяти раннера.

Червь Mini Shai-Hulud распространяется как настоящий червь: после кражи учётных данных из одного CI/CD-конвейера он автоматически находит все пакеты, которые контролирует владелец токена, и публикует их заражённые версии. Полезная нагрузка размером 2,3 МБ представляет собой обфусцированный JavaScript-файл, который содержит три слоя защиты: строковую таблицу obfuscator.io, вторичный шифр на основе перемешивания байтов и AES-256-GCM для 11 вложенных полезных нагрузок. Основной механизм кражи данных - чтение памяти процесса Runner.Worker через /proc/{pid}/mem. Специальный Python-скрипт сканирует всю доступную память в поисках JSON-объектов с меткой "isSecret":true, что позволяет извлечь даже те секреты, которые никогда не записывались на диск.

Кроме того, червь активно считывает файлы учётных данных из более чем 100 путей, охватывающих облачных провайдеров (AWS, Azure, GCP), криптовалютные кошельки, инструменты разработчика (VS Code, Claude Code), конфигурации SSH, Docker, Kubernetes, а также историю командной строки. Для AWS используется полный протокол IMDSv2 для получения временных токенов IAM. Для HashiCorp Vault применяется прямой запрос к локальному агенту.

Украденные данные шифруются через RSA-4096-OAAP и AES-256-GCM, после чего отправляются по двум каналам. Первый - CDN протокола Session (messenger.getsession.org), что делает трафик похожим на легитимный из-за принадлежности домена к приватному мессенджеру. Второй - коммиты в GitHub через API GraphQL с подменой автора на claude@users.noreply.github.com и ветками, названными в стиле мира Дюны (atreides, fremen, sandworm и т.д.), имитирующими обновления Dependabot.

Червь также устанавливает механизмы закрепления в системе: он создаёт хуки для VS Code (запуск при открытии папки), для Claude Code (запуск при старте сессии) и фоновый сервис gh-token-monitor, который переживает перезагрузку. Кроме того, он внедряет в репозитории два вредоносных workflow GitHub Actions с именем CodeQL Analysis, которые при каждом пуше или деплое сериализуют все секреты репозитория в JSON и отправляют их на C2-сервер или загружают как артефакт.

Отдельной угрозой является вымогательская функция: при создании нового npm-токена червь устанавливает описание "IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner". Если жертва отзывает этот токен, запускается процедура уничтожения данных на машине. Поэтому специалистам по безопасности настоятельно рекомендуют не отзывать токены до полной изоляции и криминалистической копии системы.

Список скомпрометированных пакетов продолжает расти. На данный момент известны @mistralai/mistralai (версии 2.2.3, 2.2.4), @tanstack/router-utils (1.161.11, 1.161.14), @tanstack/router-core (1.169.5, 1.169.8), @opensearch-project/opensearch (3.6.2), @uipath/docsai-tool (1.0.1). Если вы устанавливали какие-либо из этих версий, предполагайте, что все секреты окружения скомпрометированы.

Эта атака демонстрирует принципиально новый уровень угроз для цепочек поставок программного обеспечения. Даже формально корректные механизмы проверки происхождения (SLSA) не гарантируют безопасность, если сама среда сборки заражена. Разработчикам следует усилить мониторинг пайплайнов, использовать изоляцию раннеров и больше доверять не только статическим проверкам, но и поведенческому анализу в реальном времени.

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

Хеши вредоносной нагрузки (SHA-256)

  • router_init.js (встроен во все пакеты @tanstack): ab4fcadaec49c03278063dd269ea5eef82d24f2124a8e15d7b90f2fa8601266c
  • tanstack_runner.js (из коммита git): 2ec78d556d696e208927cc503d48e4b5eb56b31abc2870c2ed2e98d6be27fc96
  • package.json пакета @tanstack/setup: 7c12d8614c624c70d6dd6fc2ee289332474abaa38f70ebe2cdef064923ca3a9b

Домены управляющих серверов (C2)

  • api.masscan.cloud
  • filev2.getsession.org
  • git-tanstack.com
  • seed1.getsession.org

Инфраструктура злоумышленника

  • Учётная запись GitHub: voicproducoes (ID: 269549300), создана 2026-03-19
  • Email: voicproducoes@gmail.com
  • Форк: voicproducoes/router (форк TanStack/router, создан 2026-05-10)
  • Вредоносный коммит: 79ac49eedf774dd4b0cfa308722bc463cfe5885c
  • Репозитории-маркеры червя: siridar-ghola-567, tleilaxu-ornithopter-43, описаны как «A Mini Shai-Hulud has Appeared»

Сетевые цели

  • Сетевая цель: 169.254.169.254 - запрос к метаданным AWS EC2 IMDS для получения учётных данных роли IAM (IMDSv2)
  • Сетевая цель: 169.254.170.2 - учётные данные метаданных задач ECS/Fargate
  • Сетевая цель: 127.0.0.1:8200 - локальный доступ к HashiCorp Vault
  • Закрепление сертификата TLS: CN=seed1.getsession.org, O=Oxen Privacy Tech Foundation (истекает в 2033) — жёстко заданный сертификат для проверки соединения с C2

Артефакты сохранения (persistence)

  • Файл сохранения: .claude/settings.json - хук SessionStart; повторно запускает вредоносное ПО при каждом сеансе Claude Code
  • Файл сохранения: .vscode/tasks.json - задача folderOpen; повторно запускается при каждом открытии VS Code
  • Файл сохранения: .claude/router_runtime.js - полезная нагрузка Bun, записываемая для сохранения
  • Файл сохранения: .claude/setup.mjs, .vscode/setup.mjs - общие скрипты установки
  • Служба сохранения (macOS): ~/Library/LaunchAgents/com.user.gh-token-monitor.plist - LaunchAgent для постоянного мониторинга токенов GitHub
  • Служба сохранения (Linux): ~/.config/systemd/user/gh-token-monitor.service - пользовательская служба systemd для постоянного мониторинга токенов GitHub
  • Внедрённый рабочий процесс: .github/workflows/codeql_analysis.yml - извлекает все секреты репозитория при push/развёртывании

Маркеры кампании

  • Автор коммита GitHub: claude@users.noreply.github.com - автор «мёртвого ящика»; неожиданные коммиты = активная эксфильтрация
  • Сообщение коммита GitHub: chore: update dependencies - маскировочное сообщение «мёртвого ящика»
  • Шаблон имени ветки: dependabot/github_actions/format/{dune-word} = ветки «мёртвого ящика», имитирующие Dependabot
  • Словарь слов для веток кампании: термины вселенной Дюны (fremen, sandworm, harkonnen, atreides, melange и т.д.) - используются для веток «мёртвого ящика» в коммитах GitHub
  • Необязательная зависимость: github:tanstack/router#79ac49eedf774dd4b0cfa308722bc463cfe5885c - вектор выполнения при установке
  • Главный ключ шифра: 0c0e873033875f1bc471eda37e3b9d0f9b89bd41a4bbb4f86746caa2176c40aa - входной ключ PBKDF2; уникален для этой кампании
  • Соль PBKDF2: svksjrhjkcejg - вторичная соль шифра, специфичная для кампании
  • Описание токена npm: IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner - угроза выкупа; наличие указывает на компрометацию

Сигналы обнаружения

  • Наличие router_init.js в корне пакета (не в dist/ или src/)
  • optionalDependencies, указывающие на URL github: с конкретным хешем коммита
  • Аномалия размера пакета: заражённые tarball-архивы имеют размер ~900 КБ против ~190 КБ у чистых версий
  • Две версии одного пакета, опубликованные с интервалом в несколько минут (схема «двойного удара»)
  • Скрипт prepare в зависимости, запускающий обфусцированный .js-файл через Bun
  • Исходящие HTTPS-соединения к filev2.getsession.org или api.masscan.cloud во время npm install или этапов сборки
  • Неожиданные процессы python3, читающие /proc//mem во время CI-запусков
  • Токены npm с описанием IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner
  • Коммиты от claude@users.noreply.github.com с сообщением chore: update dependencies
  • Ветки, соответствующие шаблону dependabot/github_actions/format/ с терминологией Дюны

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