Вредоносные бета-версии npm-пакетов Joyfill доставляли блокчейн-загрузчик и RAT

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

28 июля 2026 года в реестр npm были загружены две вредоносные бета-версии пакетов из пространства имён @joyfill. Пакеты @joyfill/layouts@0.1.2-2773.beta.0 и @joyfill/components@4.0.0-rc24-2773-beta.4 содержали встроенный имплант, который запускался не при установке, а при импорте модуля в любом приложении. Это означает, что стандартная команда "npm install --ignore-scripts" не могла предотвратить выполнение вредоносного кода, так как он был внедрён непосредственно в файлы пакетов, а не в сценарии установки.

Описание

Joyfill - это легитимный набор инструментов для встраивания форм, документов и PDF в веб- и мобильные приложения. Оба скомпрометированных пакета имели в сумме около 16 тысяч еженедельных загрузок, и хотя затронуты были только бета-версии, точное число установивших их пользователей неизвестно. Злоумышленники использовали публикацию под существующим аккаунтом и бета-тег, чтобы не перезаписывать стабильные версии. Через несколько часов после публикации вредоносные версии были удалены, однако они остались в кэшах сторонних зеркал реестра.

Цепочка атаки: от npm до удалённого управления

Вредоносный код был встроен в скомпилированные JavaScript-бандлы пакетов. При импорте модуля исполнялся обфусцированный загрузчик, который получал управляющие команды через публичные блокчейн-транзакции. Как показал анализ компании StepSecurity, загрузчик обращался к сети Tron, оттуда извлекал хеш транзакции в BNB Smart Chain, а затем через RPC-запрос получал зашифрованное полезное содержимое. Данные расшифровывались с помощью XOR-ключа и исполнялись в памяти. Такая схема позволяла операторам менять управляющий сервер в любой момент, просто публикуя новую транзакцию, - блокировка традиционных доменов не остановила бы атаку.

Первый этап разрешения давал два независимых потока: один выполнял код непосредственно в текущем процессе, другой создавал скрытый дочерний процесс Node.js с флагами "detached: true", "stdio: "ignore"" и "windowsHide: true". Это позволяло загрузчику продолжать работу даже после завершения основного приложения. На втором этапе загрузчик на основе метки кампании ("A9-0135-3") выбирал один из трёх заранее заданных IP-адресов командного сервера и повторял блокчейн-запрос с другим набором Tron-адресов и ключей.

Финальная нагрузка: удалённый троян и похититель паролей

Третьим этапом загрузчик получал и исполнял 77-килобайтный код удалённого доступа (RAT), написанный на JavaScript и использующий библиотеку Socket.IO для связи с сервером. Восстановленный экспертами код содержал команды для сбора информации о хосте, кражи содержимого буфера обмена (через PowerShell на Windows, pbpaste на macOS и xclip/xsel на Linux), загрузки и выгрузки файлов, выполнения произвольных команд и JavaScript. Троян также мог устанавливать себе зависимости через npm прямо во время работы.

Особую опасность представлял механизм закрепления: RAT вставлял самовоспроизводящийся блок в файлы, которые регулярно используются инструментами разработчика - модуль @vscode/deviceid (применяется в VS Code, Cursor, Antigravity), основной модуль Discord Desktop, файл main.js GitHub Desktop и глобальный клиент npm. Каждая вставка помечалась уникальными комментариями, что позволяло избежать дублирования. Таким образом, заражение одного компьютера могло привести к компрометации всех проектов, с которыми работает разработчик.

Кроме того, отдельный ответ с сервера "/$/boot" загружал второй большой скрипт (около 66 КБ), который в свою очередь доставлял Python-похититель данных. Этот похититель собирал логины и пароли из браузеров (Chrome, Firefox), хранилища менеджеров паролей и криптовалютных кошельков, данные Git и GitHub CLI, а также содержимое системных хранилищ учётных данных (Credential Manager на Windows, Keychain на macOS, Secret Service на Linux). Собранная информация упаковывалась в зашифрованный ZIP-архив и отправлялась на сервер. По оценкам аналитиков, этот похититель является вариантом известного вредоносного ПО OmniStealer.

Связь с другими кампаниями и последствия

Загрузчик и его методы полностью совпадают с семейством PolinRider, а финальный RAT - с семейством DEV#POPPER, которое ранее анализировалось в связи с атакой на форк репозитория ShoeVista. Использование идентичных блокчейн-адресов и ключей шифрования подтверждает, что за обоими инцидентами стоят одни и те же злоумышленники. Отличие лишь в векторе: в случае Joyfill была скомпрометирована учётная запись мейнтейнера, а не внесён вредоносный код через пул-реквест.

Разработчикам следует немедленно удалить из блокировочных файлов и кэша все версии с пометкой 2773-beta. Пакеты @joyfill/layouts@0.1.2-2773.beta.0, @joyfill/components@4.0.0-rc24-2773-beta.4, а также более поздние чистые сборки с тем же бета-номером (например, .beta.5 и .beta.6) должны быть заменены на проверенные стабильные версии, опубликованные до 28 июля. Любое устройство, где эти пакеты были импортированы, следует считать потенциально скомпрометированным, поскольку RAT предоставляет полный удалённый доступ к системе. Необходимо изолировать такие хосты, проверить модификации перечисленных файлов инструментов разработчика, а при подозрении на выполнение Python-похитителя - сменить все пароли, сохранённые в браузерах, и ключи доступа к репозиториям.

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

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