В экосистеме npm обнаружили вредоносный пакет indexed-btree, который маскировался под библиотеку для B-деревьев и напоминал легитимный sorted-btree. Вредоносный код спрятали не в сценарии установки, а внутри обычного метода библиотеки. Он запускался уже во время работы приложения.
Checkmarx сообщила, что счетчик indexed-btree приближался к 2 млн загрузок в неделю. Девять связанных пакетов суммарно набрали еще более 5,3 млн загрузок. Пакет начали помечать как вредоносный еще до публикации подробного технического разбора. В базе OpenSSF запись о indexed-btree появилась 3 сентября, а Snyk опубликовала предупреждение 7 сентября. Сейчас в npm доступна защитная заглушка 0.0.1-security, а исходное вредоносное содержимое удалено.
Большая часть атак через npm традиционно использует сценарии жизненного цикла preinstall, install или postinstall. Пользователь устанавливает зависимость, после чего менеджер пакетов автоматически запускает прописанный автором код.
В indexed-btree такого сценария не было. Исследователи нашли загрузчик внутри BTree.prototype.set() — обычного метода библиотеки, который отвечает за запись данных в дерево. Код проверял наличие файла extended/sharedLoad.min.js. Если приложению передавался ключ со значением 100, файл запускался отдельным процессом Node.js.
Процесс создавался с отключенным стандартным вводом и выводом, отделялся от родительского приложения и продолжал работу самостоятельно.
Это значит, что сама команда установки не обязательно запускала вредонос. Простого присутствия пакета в node_modules тоже недостаточно для активации именно этой ветки. Загрузчик срабатывал во время использования библиотеки при выполнении заданного условия.
Checkmarx при этом отмечает, что set() относится к основным методам библиотеки и в реальных приложениях может вызываться регулярно. Поэтому спрятанная внутри него проверка имела хорошие шансы рано или поздно выполниться.
Механика атаки хорошо ложится на изменения безопасности npm. В npm 12, который стал общедоступен 8 июля, сценарии preinstall, install и postinstall у зависимостей по умолчанию отключены. Проект должен отдельно разрешить выполнение таких сценариев. npm также перестал автоматически разрешать зависимости из Git и удаленных URL без явного разрешения.indexed-btree не взламывал эту защиту. Он просто не использовал защищаемый механизм.
Установка выглядела обычной, потому что в ней не было опасного сценария. Запуск происходил позднее — из кода самой библиотеки.
Это важное ограничение новой защиты npm: блокировка install-скриптов сокращает один класс атак, но не может гарантировать безопасность JavaScript, который зависимость выполняет уже внутри приложения. Авторы indexed-btree подготовили для пакета отдельный GitHub-репозиторий с историей изменений и внешне нормальной активностью.
При этом вредоносного кода, найденного в опубликованном npm-пакете, в открытом репозитории не было. Checkmarx обращает на это внимание: разработчик мог проверить исходники по ссылке из описания пакета и не увидеть того, что фактически попадало в архив из npm.
Для атак на цепочку поставок это существенная деталь. GitHub-репозиторий и пакет, опубликованный в реестре, не обязаны содержать полностью одинаковые файлы. Проверка исходников в репозитории поэтому не заменяет анализ самого tarball, который скачивает менеджер пакетов.
После запуска sharedLoad.min.js вредонос собирал сведения о системе. Checkmarx обнаружила сбор имени компьютера, архитектуры ОС, информации о процессоре, объема памяти и времени работы. Результаты отправлялись через заранее прописанные Slack- и Telegram-каналы.
Такие данные сами по себе не дают полный доступ к машине, но позволяют оператору понять, где сработал пакет и насколько интересна конкретная система.
Для атакующего особенно ценны среды разработчиков и сборочные серверы: там могут находиться токены npm, ключи репозиториев, облачные учетные данные и другие секреты. Опубликованный разбор indexed-btree, однако, не подтверждает автоматический сбор всех этих категорий данных самим первым этапом, поэтому приписывать ему полноценный стилер без дополнительных доказательств нельзя.
Следующий этап был устроен сложнее. Вредонос обращался к смарт-контракту в тестовой сети Ethereum Sepolia. Контракт использовался в управляющей инфраструктуре и участвовал в получении зашифрованной второй стадии.
Загрузчик генерировал пару ключей X25519 и получал открытый ключ оператора через контракт. Затем стороны вычисляли общий секрет с помощью обмена ключами на эллиптических кривых. Из него выводился ключ AES, которым расшифровывалась следующая полезная нагрузка.
Checkmarx пишет о двух зашифрованных фрагментах, которые после расшифровки объединялись во вторую стадию вредоноса.
Смысл такого подхода в устойчивости инфраструктуры. Обычный управляющий домен можно заблокировать или изъять. Данные, опубликованные через блокчейн, убрать таким же способом значительно сложнее.
В коде также нашли функции очистки. Вредонос мог удалять собственные файлы и убирать добавленный фрагмент из основного метода библиотеки. Это затрудняет расследование уже после запуска: текущее содержимое node_modules может отличаться от того, что находилось там в момент заражения.
При проверке потенциально затронутой системы поэтому полезны не только существующие файлы, но и package-lock.json, yarn.lock, pnpm-lock.yaml, кеши npm, сохраненные архивы зависимостей и журналы CI/CD.
Checkmarx обнаружила еще девять пакетов, связанных с той же инфраструктурой: ordered-kv-index, btree-leaderboard, priority-slot-queue, btree-range-store, btree-core, btree-time-index, btree-lru-cache, neighbor-key-map и sliding-score-window.
На момент исследования их показатели загрузок выглядели так: btree-core — 1 951 274, btree-leaderboard — 493 685, btree-range-store — 468 092, ordered-kv-index — 448 184, sliding-score-window — 448 024, btree-time-index — 425 312, priority-slot-queue — 402 860, btree-lru-cache — 372 185 и neighbor-key-map — 366 019.
В сумме получается около 5,38 млн загрузок. Эти пакеты были удалены из npm.
Тот же Ethereum-контракт исследователи ранее встречали в пакете mutex-forge, что позволило связать эпизоды между собой.
Checkmarx также связала с оператором адрес, на котором находилось около 109 ETH. На момент исследования это соответствовало примерно €231 тыс.
Сам отчет формулирует это как возможный заработок атакующего. При этом происхождение средств не установлено.
BleepingComputer отдельно отмечает, что из опубликованных данных нельзя сделать вывод, будто эти 109 ETH были украдены через indexed-btree или остальные пакеты кампании.
Вопросы и ответы
Что такое indexed-btree?
Это вредоносный npm-пакет, который выглядел как библиотека для работы с B-деревьями и имитировал легитимный sorted-btree.
Вредонос запускался сразу после npm install?
Нет. В пакете не было необходимого для этого preinstall, install или postinstall. Загрузчик находился внутри BTree.prototype.set() и запускался во время работы приложения.
При каком условии срабатывал загрузчик?
Checkmarx обнаружила проверку key == 100. При выполнении этого условия и наличии extended/sharedLoad.min.js файл запускался отдельным процессом Node.js.
Каждая загрузка пакета означает заражение?
Нет. Счетчик npm показывает загрузки пакета, а не число уникальных компьютеров или успешных запусков вредоноса.
Что собирал первый этап?
Имя компьютера, архитектуру системы, данные о процессоре, памяти и времени работы. Информация отправлялась через Slack и Telegram.
Зачем использовали Ethereum?
Смарт-контракт в сети Sepolia использовался как часть управляющей инфраструктуры и механизма получения зашифрованной следующей стадии. Это делает схему менее зависимой от обычного домена или одного сервера.
Помогает ли защита npm 12?
Она блокирует по умолчанию сценарии установки зависимостей и закрывает важный класс атак. indexed-btree такие сценарии не использовал, поэтому его вредоносная логика находилась за пределами этого механизма защиты.
Сколько компьютеров заражено?
Неизвестно. Почти 2 млн — показатель недельных загрузок indexed-btree, а не число подтвержденных жертв.
Пакет еще можно установить?
Исходное вредоносное содержимое удалено. В реестре сейчас используется защитная версия-заглушка 0.0.1-security.
Что делать, если пакет использовался?
Проверить версии и историю сборок. Если вредоносная версия выполнялась, систему стоит считать потенциально скомпрометированной, восстановить из доверенного состояния и сменить доступные ей секреты с другой чистой машины.
Есть новость? Станьте автором.
Мы сотрудничаем с независимыми исследователями и специалистами по кибербезопасности. Отправьте нам новость или предложите статью на рассмотрение редакции.