Путаница с зависимостями: 25 вредоносных пакетов npm нацелены на кражу учётных данных разработчиков

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

Первого июля 2026 года пользователь npm marketfront за три минуты опубликовал 25 пакетов в область @marketfront. Все пакеты имели версию 7.0.0 и содержали постинсталляционный сборщик учётных данных. В README каждого пакета значилась одна и та же строка: "Internal package - Platform Engineering Team". Эта строка не указывает на конкретный аккаунт, а представляет собой многоразовый шаблон, использующий технику dependency confusion. Набор пакетов имитировал внутренние модули для фронтенда интернет-магазина - header, footer, navbar, bannerpopup, customdealsfeed и другие.

Описание

Специалисты SafeDep отслеживают эту кампанию с мая 2026 года. Ранее были зафиксированы волны с участием аккаунтов mr.4nd3r50n, pik-libs, t-in-one и emcd-vue, которые публиковали пакеты в областях @cloudplatform-single-spa, @t-in-one, @emcd-vue и других. Об этом сообщается в отчёте SafeDep, где документируется вся кампания под названием oob.moika.tech. Реестр npm уже удалил пакеты @marketfront, однако шаблон сохранился в действующей области @emcd-vue, которая была перевыпущена с новыми версиями - @emcd-vue/auth@7.1.0, @emcd-vue/loans@7.2.0, @emcd-vue/b2b-pay-form@5.8.0. Эти артефакты побайтно совпадают с шаблоном, что позволило SafeDep провести статический анализ.

Вредоносная кампания эксплуатирует механизм dependency confusion. Если проект использует имя пакета, соответствующее внутреннему пространству имён, но не заблокировал его в частном реестре, менеджер пакетов npm может разрешить публичную версию вместо отсутствующей приватной. Именно так злоумышленники добиваются выполнения своего кода на рабочих станциях разработчиков и в сборочных конвейерах CI. Пакеты @marketfront имитируют внутренние модули конкретной организации, что повышает шансы на срабатывание атаки.

После установки пакета npm выполняет скрипт "scripts/postinstall.js". Этот скрипт представляет собой одну строку обфусцированного кода размером около 160 КБ, созданного с помощью obfuscator.io. Первый уровень обфускации - стандартная перестановка строк через модифицированный алфавит base64. Однако чувствительные строки скрыты за вторым слоем шифрования RC4 с ключом для каждой строки. Из-за этого пути к файлам учётных данных и адрес командного сервера не видны при статическом дампе строк. SafeDep восстановил логику поведения методом статической декомпиляции.

Полезная нагрузка обращается к модулям "fs", "os", "http", "https", "zlib", "path" и "dns". Она читает около 20 файлов, содержащих учётные данные: ключи SSH из "~/.ssh", облачные credentials ("~/.aws/credentials", "~/.kube/config", "~/.docker/config.json"), токены реестров и пакетов ("~/.npmrc"), сетевые и базовые secrets ("~/.netrc", "~/.pgpass"), git-credentials ("~/.git-credentials"), файлы окружения и историю команд ("~/.env", "~/.bash_history"). Собранные данные сжимаются в gzip и отправляются через HTTPS POST с кастомным заголовком "X-Secret" по пути "/api/v1/events". Дополнительно выполняется DNS-запрос на командный сервер. Адрес сервера скрыт за слоем RC4 и XOR вокруг встроенного блока конфигурации. SafeDep не восстанавливал его без выполнения кода.

Шаблон README и метаданные остаются неизменными на протяжении всей кампании, в то время как полезная нагрузка эволюционирует. Первая волна в конце мая использовала версии 99.99.99, собирала переменные окружения и отправляла их на сервер oob.moika.tech. Волна в начале июня применяла более сложную обфускацию WaCk/JScrambler, устанавливала персистентность в домашний каталог и использовала рукопожатие второго этапа FUSION. Версия от 1 июля перешла к целевому сбору файлов учётных данных с шифрованием командного сервера. Версионные номера при этом стали выглядеть естественно - 7.0.0, 7.1.0, 7.2.0, что не срабатывает на эвристиках, проверяющих аномальные версии вроде 99.99.99.

Два элемента остались постоянными: маркер README "Internal package - Platform Engineering Team" с подставными доменами вроде docs.<scope>.io и jira.<scope>.io, а также заголовок X-Secret в исходящих запросах. Этот маркер является дешёвым элементом атаки, но его трудно использовать для обнаружения на поисковых серверах npm, так как поиск не индексирует содержимое README, а свежие пакеты с нулевыми загрузками получают низкий приоритет.

Для выявления подобных угроз SafeDep рекомендует использовать сигнатуры метаданных и поведения. К таким сигнатурам относятся: постинсталляционный скрипт "node scripts/postinstall.js" с однострочным обфусцированным пайлоадом, автор поля "author", заканчивающийся на "Platform Engineering", подставные домены в полях "repository.url", "bugs.url", "homepage", описание, взятое из фиксированного набора "Internal … loader / logger / client", и исходящий трафик во время установки с заголовком X-Secret.

Меры защиты включают блокировку каждой внутренней области в файле ".npmrc" для частного реестра. Без такой блокировки менеджер пакетов будет обращаться к публичному реестру, когда приватная версия недоступна. Если какой-либо пакет @marketfront версии 7.0.0 был установлен на рабочую станцию или сборочный узел, систему следует считать скомпрометированной. Необходимо немедленно ротировать ключи SSH, облачные учётные данные, токены npm, git-credentials и любые secrets, присутствовавшие в "~/.env" или окружении. Рекомендуется также проверять сетевые журналы на наличие HTTPS-запросов с заголовком X-Secret по пути "/api/v1/events" и на необычную активность DNS во время выполнения npm install. Добавление указанных сигнатур метаданных и поведения в правила триажа позволит автоматически выявлять следующие ротации области, не ожидая ручного обнаружения.

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

Package

  • @marketfront/actualordersnippetpopup
  • @marketfront/advertisingdevtool
  • @marketfront/bannerpopup
  • @marketfront/baobabtech
  • @marketfront/basemarkettemplate
  • @marketfront/blenderdevtool
  • @marketfront/captchaservice
  • @marketfront/changefilter
  • @marketfront/commonecommerce
  • @marketfront/customdealsfeed
  • @marketfront/designsystemdevtool
  • @marketfront/devtoolsloader
  • @marketfront/digitalherobannercarousel
  • @marketfront/dynamicpageparams
  • @marketfront/errorcounter
  • @marketfront/fashiononboardingpopup
  • @marketfront/fingerprint
  • @marketfront/footer
  • @marketfront/gotoauthpopup
  • @marketfront/header
  • @marketfront/infopopup
  • @marketfront/livestreampreviewpopup
  • @marketfront/madvpopup
  • @marketfront/mychatspreloader
  • @marketfront/navbar

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