Разработчики по всему миру столкнулись с атакой, которая использует механизмы доверенной публикации против самих же авторов. Исследователи из компании JFrog опубликовали отчёт о продолжающейся кампании Shai-Hulud: Here We Go Again. Она затронула более ста семидесяти пакетов в реестре npm и два пакета в индексе PyPI. Совокупное количество загрузок этих пакетов превышает двести миллионов в неделю. Вредоносные компоненты содержат загрузчик и зашифрованную полезную нагрузку, которая запускается в средах разработки и системах непрерывной интеграции и доставки, ворует учётные данные и использует их для публикации дополнительных скомпрометированных пакетов.
Описание
Принципиальная особенность этой кампании - червеобразное поведение и деструктивный переключатель, срабатывающий при отзыве украденных токенов. В отличие от многих атак, нацеленных лишь на кражу данных, здесь вредонос активно ищет возможность опубликовать новые версии заражённых пакетов. Для этого он модифицирует архивы, увеличивает номер патча и вставляет вредоносные метаданные. Таким образом, один скомпрометированный сборочный сервер становится точкой распространения инфекции сразу на несколько экосистем.
Цепочка заражения началась с выполнения вредоносного кода в доверенном окружении GitHub Actions. Злоумышленник использовал шаблон workflow, который позволял коду из форка выполняться в привилегированном контексте основного репозитория. Этот метод становится всё популярнее из-за защитного механизма npm под названием "доверенная публикация". Внедрённый код отравил запись кэша сборки, а при следующей легитимной сборке этот кэш был восстановлен. В результате вредоносный пакет был опубликован от имени доверенного maintainer’а, что делает проверки происхождения бесполезными: подпись может подтвердить, где собран артефакт, но не может гарантировать, что сам workflow был чист во время сборки.
После выполнения в среде релиза вредонос извлёк из памяти runner’а OIDC-материалы (протокол аутентификации на основе OpenID Connect), обменял их на токены публикации npm и выпустил скомпрометированные версии пакетов. Логика распространения встроена прямо в полезную нагрузку, а не является разовым действием атакующего. Это означает, что каждая новая заражённая машина может заразить следующие пакеты без участия человека.
Анализ npm-варианта показывает многоступенчатую обфускацию. Пакет содержит скрипт предустановки, который загружает среду выполнения Bun, распаковывает её во временную папку и запускает основную полезную нагрузку. Сама нагрузка зашифрована несколькими слоями: используется PBKDF2-SHA256 и AES-256-GCM, что делает статический анализ крайне затруднительным. После расшифровки вредонос выполняет широкий сбор учётных данных: токены GitHub, npm, AWS, ключи Kubernetes, токены HashiCorp Vault, а также локальные секреты из конфигурационных файлов, переменных окружения и shell-истории.
Особую опасность представляет сбор OIDC-токенов из памяти runner’а. Это позволяет даже без долгоживущих токенов получить доступ к публикации новых пакетов. Для эксфильтрации используются несколько каналов: сессионная сеть Session/Oxen, GitHub GraphQL API и создание публичных репозиториев-приёмников. Вредонос также устанавливает монитор токенов, который раз в минуту проверяет валидность украденного GitHub-токена. Если токен отозван, монитор выполняет команду rm -rf ~. Таким образом, прежде чем отзывать скомпрометированные токены, необходимо изолировать заражённую машину и удалить персистентные компоненты.
PyPI-вариант использует другой механизм запуска. В пакет mistralai@2.4.6 внедрён загрузчик, срабатывающий при импорте библиотеки. Он загружает с удалённого сервера Python-приложение transformers.pyz. Изначально этот файл содержал только текст с именем группировки, но позже был заменён на полноценный крадущий credentials инструмент. Обновлённая версия собирает учётные данные из тех же источников, что и npm-вариант, и добавляет кражу из менеджеров паролей 1Password, Bitwarden, pass и gopass. После сбора данные шифруются с использованием RSA и AES-256-GCM и отправляются на сервер управления. Если прямой канал блокируется, вредонос может найти резервный адрес через поиск специальных меток в коммитах GitHub.
Ещё одна тревожная деталь - второй этап PyPI-атаки, содержащий геоограниченное разрушительное поведение. Если система находится в часовых поясах Израиля или Ирана, с вероятностью один к шести загружается звуковой файл, который воспроизводится на полную громкость, после чего выполняется rm -rf /*. Такой деструктивный функционал делает даже единичный импорт заражённого пакета критическим инцидентом.
Масштаб кампании наглядно демонстрирует, насколько уязвимы цепочки поставки программного обеспечения. Даже защита на основе доверенной публикации не спасает, если злоумышленник получил выполнение внутри workflow. Разработчикам и администраторам следует пересмотреть свои процессы: проверять не только метаданные пакетов, но и поведение сборочных пайплайнов, особенно восстановление кэша и использование OIDC. Любой пакет, установленный за последние дни, должен быть проверен на наличие вредоносных скриптов предустановки или необычных импортов. В случае обнаружения заражения необходимо изолировать пострадавшую среду, удалить механизмы персистентности и только после этого отзывать скомпрометированные токены - в противном случае сработает деструктивный переключатель.
Кампания Shai-Hulud: Here We Go Again - это ещё одно доказательство того, что атаки на цепочку поставок становятся всё более сложными и червеобразными. Защита требует комплексного подхода: автоматического анализа пакетов, контроля целостности сборочных сред и быстрого реагирования на инциденты.
Индикаторы компрометации
IPv4
- 83.142.209.194
Domains
- api.masscan.cloud
- git-tanstack.com
- seed1.getsession.org
- seed2.getsession.org
- seed3.getsession.org
URLs
- http://filev2.getsession.org/file/
- https://83.142.209.194/audio.mp3
- https://83.142.209.194/transformers.pyz
- https://83.142.209.194/v1/models
- https://83.142.209.194/v1/weights
- https://api.github.com/graphql
- https://api.github.com/repos///contents/results/
- https://api.github.com/search/commits?q=FIRESCALE
- https://api.github.com/user/repos
SHA256
- 29c729852fce5a53e30a1541d9fec79c915b2e13f1eda94a5978cf0aae0d88d9
- 2a314ea8be337e1ca9ec833ed13ed854d9fd38bce0a519cf288f3bec8d9e6f30
- 2ec78d556d696e208927cc503d48e4b5eb56b31abc2870c2ed2e98d6be27fc96
- 5245eb032e336b85cff0dbb3450d591826bf2ef214fd30d7eba1a763664e151b
- ab4fcadaec49c03278063dd269ea5eef82d24f2124a8e15d7b90f2fa8601266c
- D4a2086ea18f5e39cd867b8b06918a524eabb21d45ea98aad07357b98173458a