Скомпрометированный npm-пакет dforge-mcp: 105 минут чужого доступа и цепочка доставки, которая работает до сих пор

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

Девятого сентября 2026 года чужая активность в учётной записи сопровождающего npm-пакета dforge-mcp продлилась 105 минут. За это время в проекте появился файл-загрузчик, сборочный конвейер перестал ждать ручной команды и стал выпускать версии самостоятельно, а на реестр ушла сборка 0.2.21 с вредоносной вставкой. Актуальной она оставалась 35 минут 38 секунд, после чего сопровождающий отозвал её и опубликовал чистую 0.2.22. Сам пакет служит для работы с модулями dForge и распространяется через npm - крупнейший реестр библиотек для JavaScript.

Описание

Реестр при этом ничего не нарушал и никого не подводил. Публикация прошла через доверенную сборку, при которой реестр доверяет удостоверению сборочного конвейера, а не отдельному паролю. Право записи в основную ветку автоматически превратилось в право публикации, поэтому пароль самого реестра злоумышленнику не понадобился. Подписей он не подделывал, уязвимостей не использовал, действовал чужой действующей учётной записью. Как именно он её получил, авторы разбора прямо не утверждают. Наиболее правдоподобная версия - учётные данные разработчика, сохранённые на заражённой машине. Двухфакторная аутентификация такую кражу не останавливает: токены и ключи хранятся именно для того, чтобы автоматика работала без участия человека.

Масштаб за пределами одного пакета шире, чем следует из истории с отозванной версией. По данным CloudSEK Global Threat Intelligence, тот же файл-загрузчик обнаружен в 65 публичных репозиториях, принадлежащих 22 учётным записям. Восемь из них держат 45 репозиториев: одна украденная учётная запись открывает доступ ко всем проектам, куда она может писать. Сам файл почти целиком состоит из обычного рабочего кода - около 99 килобайт, а сторонний код загружает и выполняет единственная строка. Запутанных фрагментов в файле нет, поэтому поиск по признакам запутывания ничего не даёт. Маскировка держится на объёме и правдоподобии: рецензент видит правдоподобный набор вычислительных процедур и не находит повода читать дальше.

Вредоносная часть собирается по цепочке из четырёх этапов. Первый этап - короткий сценарий, который определяет систему и записывает метку кампании в служебный файл. Второй скачивает следующую программу под безобидным расширением, переименовывает её и запускает в фоне, попутно очищая экран, чтобы работа установщика осталась незамеченной. Третий этап скачивает среду выполнения Node.js (платформу для запуска программ на JavaScript) с официального сайта и распаковывает её в папку загрузок пользователя. Так обходится защита на машинах, где Node.js не установлен: полезная нагрузка приносит собственный интерпретатор. Четвёртый этап - программа удалённого управления, которая при запуске удаляет собственный файл с диска и потому не оставляет следов для обычного поиска. Машина с работающим процессом выглядит чистой, а соединение с командно-контрольным сервером при этом поддерживается.

Адрес управляющего сервера вредоносная программа получает необычным способом. Собственного домена у неё нет вовсе. Конфигурацию оператор публикует в публичном блокчейне: она записана в поле получателя пустой транзакции, которая обходится примерно в двадцать центов. Такой канал нельзя закрыть через регистратора, сервер - изъять, а учётную запись - заблокировать. Проверка пяти дней спустя показала, что все четыре размещённых этапа по-прежнему отвечают на запросы, и ни один из них не привязан к конкретному пакету. Подключить к этой цепочке второго скомпрометированного издателя стоит оператору одной замены строки.

Следы ведут дальше одного пакета. В разборе упоминается кампания PolinRider, которую исследователи проекта OpenSourceMalware отслеживают с марта 2026 года. Её характерный признак - полезная нагрузка, молча дописанная в конец настоящего файла конфигурации проекта и невидимая при обычном просмотре. Авторы отчёта отдельно оговаривают, что связь кампании с КНДР взята из чужой публикации, а не установлена ими, и одна доступная им независимая проверка этого не подтвердила. Оговорка существенная: похожие имена и приёмы встречаются у нескольких групп, а опубликованные полгода назад индикаторы мог скопировать другой оператор.

Практический ущерб пока выглядит ограниченным. За неделю с инцидентом реестр зафиксировал 115 обращений к версии 0.2.21 и 145 обращений к неудачной 0.2.20, тогда как предыдущая чистая версия собрала 59. Речь идёт о скачиваниях, а не об установках: в число попадают зеркала, кеши сборочных систем и инструменты безопасности. Неудачная сборка к тому же ломала установку пакета, поэтому в тот период поставить его не мог никто. Выполнение кода требовало дополнительного условия - запуска сервера, а не простой установки. Никаких подтверждений успешного взлома какой-либо организации в отчёте нет.

Отдельная проблема в том, что о компромиссе почти нигде не сказано. Публичного предупреждения нет ни в базе уязвимостей OSV (открытом хранилище сведений об уязвимостях), ни в консультациях GitHub, ни в блоге издателя, ни в описании пакета; изменённые версии не помечены как отозванные. Единственное внятное упоминание осталось в сообщении служебного изменения. Человек, у которого в списке зависимостей закреплена версия 0.2.21, уведомления не получит и по обычным каналам ничего не заподозрит.

Что делать сейчас: заблокировать два хост-имени оператора и исходящие соединения на его порт, закрепить пакет на версии 0.2.22, а поиск на машинах вести по сопутствующим артефактам, поскольку сама вредоносная программа удаляет себя при старте. Полезно также следить за изменениями блока запуска в сборочных конвейерах и за переопределением точек входа пакета: и то и другое видно при обычном сравнении версий, без чтения кода.

Отзыв версии закрывает только реестр. Учётные записи, через которые писал злоумышленник, остались доступны ему и после отката, а загрузчик никуда не делся из десятков репозиториев. Вывод об отсутствии ущерба на основании одной лишь отозванной сборки был бы преждевременным.

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

IPv4

  • 166.88.73.46
  • 193.247.144.38

IPv4 Port Combination

  • 193.26.115.131:15152

Domain

  • brightlaunch-ext75642.vercel.app
  • primevector-app924560.vercel.app

URL

  • http://brightlaunch-ext75642.vercel.app/api/g
  • http://brightlaunch-ext75642.vercel.app/api/handle
  • http://brightlaunch-ext75642.vercel.app/api/l
  • http://brightlaunch-ext75642.vercel.app/api/m
  • http://brightlaunch-ext75642.vercel.app/api/package
  • http://brightlaunch-ext75642.vercel.app/api/tokenl
  • http://brightlaunch-ext75642.vercel.app/api/tokenw
  • http://brightlaunch-ext75642.vercel.app/api/w
  • https://primevector-app924560.vercel.app/api/key?mem=ghappier

SHA256

  • 4e1c1b0d2cfc72c9c4f3742e64630f0a2f2e835f197d17a90b6d752310bb0bbc
  • 50ed4f880d6d38ee26f410fc6bd9b734e4857b3a94ee6ef152e7e1158bd83153
  • 63b62c6d9c35fed72a518189f63889d7aee9aed2512754b8ec264807c6970592
  • a434ca08be20d88f4e731bd1085651f7de8e053dbd9966583114c83cb8092c99
  • a85c6955ad689aa89eaae8c8b30723935274f069aed044489cfd922b46bcf2f7
  • ab851915d7b35e1b64e315f07ee7232d1d49a2349a5686f35dc8fc9bf9c2809e
  • b7c0eac8030f829388f4205d2a4c58298f63ec6672db19702dd6b3bbd08cdebd
  • bb36446376455c96b574567b7ee849657d8fe15ace897f460ad3fb867be9aafc
  • c8337c06f4d3e79dd65aca4058b7a32c763468cdc7df76a849c55395d703a40d
  • cc8502cb19ef30fe2ce68306e800bbb343e5da6799a57a91d1ebb4cb027029a5
  • ce96b204c0371df053cc6fcfdd9ead0fc0fbbbb23053dad01e2e6d4ef16e4d92
  • e5c7ee2c1a3a2e8ca4a0ccda8376e387f25e410b0ca9550e870694e65c145b4c
  • e9045b27557e5019fe44a1d5ae4b77714faa65fef883127ca7db85b7970afab4
  • f2c8234c00b1f5b0b135534bf51207dc0239647890b73531996d60c90cf9e866

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