Пакет @ethersphere/bee-js версии 13.3.0 - JavaScript-клиент сети хранения Swarm - выполняет сторонний код во время установки. Хук postinstall загружает второй этап, который забирает ключ кошелька узла Swarm и ставит бэкдор. Через полчаса тот же загрузчик появился в Swarm Desktop 0.56.2, а это приложение хранит ровно те файлы, которые нужны второму этапу. npm удалил вредоносную версию, последней чистой остаётся 13.2.0, и указатель latest снова смотрит на неё. Машину, где установили любую из двух сборок, авторы отчёта предлагают считать скомпрометированной.
Описание
Публикация прошла через штатный конвейер проекта. Релизный бот bee-worker создал коммит без подписи, которого нет ни в одной ветке, тег v13.3.0 и GitHub-релиз - всё в одну секунду. Релиз запустил рабочий процесс сборки и публикации. Тесты в нём предусмотрены только для кода, отправленного в ветку, поэтому здесь не выполнялись ни разу. Аттестация происхождения npm оказалась действительной: реестр не видел подмены. Защита ветвей не помогла, потому что ветку не использовали вообще. Предположительно скомпрометированы учётная запись бота или его токен, а не токен npm.
Загрузчик занимает 18 строк в начале скрипта, который генерирует номер версии. Две правки в манифесте пакета делают остальное: вместо prepare бот указал postinstall, а каталог со скриптами внёс в список публикуемых файлов. Теперь скрипт уезжает в пакет и срабатывает при каждой установке. По данным исследователей, хук глушит ошибки и таймауты, поэтому установка проходит без предупреждений, а в комментариях запрос назван проверкой аналитики. Сервер выбирает полезную нагрузку по имени операционной системы, которое подставлено в заголовок User-Agent. Без отдельного заголовка-маркера он отдаёт безобидную заглушку.
Второй этап написан на обычном JavaScript, под каждую систему отдельный файл. Полезная нагрузка читает config.yaml и файл ключа swarm.key из каталога данных Swarm Desktop и отправляет оба на сервер. swarm.key - зашифрованное хранилище ключа Ethereum, но пароль от него Swarm Desktop держит в открытом виде в том же конфиге. Пары файлов достаточно, чтобы получить приватный ключ узла и распорядиться средствами кошелька, деньгами чека и стейком.
На Linux и macOS ставится Go-программа evjawbreaker - удалённая оболочка. Она соединяется с управляющим узлом по TLS без проверки сертификата, запускает sh в псевдотерминале и возвращает вывод. При обнаружении отладчика или виртуальной машины выходит до подключения. Адрес узла в бинарник не вшит: программа читает его из смарт-контракта в Ethereum через публичные RPC. Домена, который можно отключить, у такой схемы нет, а оператор меняет узел одной транзакцией. Контракт развернули ещё в марте, с тех пор он указывал на разные узлы; текущий выставлен за полчаса до вредоносного коммита. Для перезапуска служит служба systemd с именем, которое выдаёт её за вспомогательный инструмент Solidity, или LaunchAgent на macOS.
На Windows работает другая связка. Скрипт повышения прав использует публичный обход UAC через профилировщик .NET в процессе mmc.exe; если пользователь отказывается, запрос показывается снова. С правами администратора скрипт ставит клиент ScreenConnect от ConnectWise - подписанный, версии 25.4.3, - и запускает его службой с правами SYSTEM. В самом клиенте изменены только настройки: адрес релея, скрытые значки в трее и баннеры, сеансы без согласия пользователя. Программные файлы остаются легитимными, поэтому их хеши ничего не показывают; отпечаток дают адрес релея, идентификатор экземпляра службы и хеши конфигурации.
Что осталось неясным. Сборка .deb для amd64 содержит загрузчик внутри архива приложения app.asar; остальные установщики вышли из той же сборки и, предположительно, содержат то же самое. Файл автообновления для Windows к утру следующего дня скачали 17 раз, поэтому часть уже установленных копий могла перейти на вредоносную версию. Как именно получили доступ к учётной записи релизного бота, не установлено. Подтверждена вредоносность опубликованных сборок; дальнейшее распространение описано по данным анализа, а не по наблюдениям за атаками.