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-похитителя - сменить все пароли, сохранённые в браузерах, и ключи доступа к репозиториям.
Индикаторы компрометации
Package
- @joyfill/layouts@0.1.2-2773.beta.0
- @joyfill/components@4.0.0-rc24-2773-beta.4
IPv4
- 166.88.134.62
- 198.105.127.210
- 23.27.13.43
- 23.27.202.27
IPv4 Port Combination
- 166.88.134.62:443
- 166.88.134.62:80
- 198.105.127.210:443
- 198.105.127.210:80
- 23.27.202.27:27017
- 23.27.202.27:443
Domain
- api.trongrid.io
- bsc-dataseed.binance.org
- bsc-rpc.publicnode.com
- fullnode.mainnet.aptoslabs.com
SHA256
- 1352ad22c99983d91e600348b7cbf58235131b1ee34cea9f09623206d5b7dea7
- 26351aed0397158d3a3b8cc8fd3047d4c015d264c9895f10f20f1521b974ed18
- 26e679eaf1e9baeb7c55eb48db482301171d4d26e1728544b23734a90dc70e1b
- 2cfede38fb121a71a2f3607474aa8cd588a99f51b37e5e6f0d8cb789fa275032
- 36ff00b45e67baa7e3674b0c80f48e88737264c61e5c6b3b091200972de8157c
- 5f6a92006ca2ea4b464d66fb41af777edce7296939a7c6ee491e2b3cbfe09848
- 67c6ef602cc850f10d935fee53fa40440df841adf081563bf4fc2631a71249ce
- 78f0de8682e0e894a5784eb7e95db4da6088f528918ca3107dd1e76f80a561d8
- 8e8b90dedd456ded0c5748119836e1ca1066112bc569c1b41ca70eb931d1d4dc
- adc4af90540d33cd1e98f44b51482ae9250fbeb97d6f8d7841c81b618cb2c6e6
- ae7565109fd01b88d82acf7f73ab20709cbc2c9f26fdea13e429ccc87a55d4fb
- bcc93dc55bc7daedf4ca57254f0e7a7f1c40e09851eab98fe10cde801982db17
- c5742ea1875ecd2360022624149994909cd0546e221e4203dffd01f48de45469
- cb46f12d70824ea24ed1f8bcf45bf3f86680e02a9089aafc03b27f691be57be3
- f452f9cfa539f4a1fe25187a99a484391290d5dbaa422ba455edf6b04f81b7d1