Светлое рабочее место аналитика утром: планшет с записью whois о просроченном домене, рядом распечатка дерева зависимостей npm и схема атаки на стеклянной доске за спиной.


При аудите зависимостей fintech-сервиса я наткнулся на пакет, мейнтейнер которого привязал npm-аккаунт к email на домене со статусом pendingDelete в whois. Домен можно было зарегистрировать за стоимость чашки кофе. Дальше - сброс пароля npm, публикация patch-версии с произвольным payload, и каждый downstream-проект с ^ или ~ в semver-диапазоне подхватил бы вредоносный код при ближайшем npm install. Между смертью домена и моей находкой прошло больше двух месяцев - и никто не заметил. Это не теория: по данным Cymulate, в 2022 году аналогичным образом предположительно был скомпрометирован Python-пакет ctx через захват истёкшего домена мейнтейнера, а в 2025–2026 годах паттерн привёл к крупнейшим инцидентам в npm - от node-ipc до самораспространяющегося червя Shai-Hulud.

Бизнес-логика атаки: от регистрации домена до credentials проекта​

Supply chain атака npm через захват домена мейнтейнера - одна из самых дешёвых форм initial access. В терминах MITRE ATT&CK это Compromise Software Supply Chain (T1195.002, Initial Access): публикация вредоносной версии существующего легитимного пакета через захваченный аккаунт. Вложения атакующего: стоимость регистрации домена и время на разведку. На выходе - контроль над npm-аккаунтом и возможность публиковать вредоносные пакеты npm с массовым downstream-эффектом. Подробнее - в нашем подробном разборе атаки на цепочку поставок.

Согласно CrowdStrike Global Threat Report 2025, 86% атак имеют финансовую мотивацию, и компрометация зависимостей JavaScript используется для:
  • Кражи cloud credentials (AWS, GCP, Azure) из CI/CD-окружений - ключевая цель: переменные окружения с токенами
  • Перехвата криптовалютных транзакций через browser API hooking (кейс chalk/debug)
  • Развёртывания самораспространяющихся червей, автоматически заражающих пакеты жертвы
  • Деструктивных действий: dead man's switch в Shai-Hulud 2.0 уничтожал домашнюю директорию при неудачной эксфильтрации
Полная цепочка MITRE ATT&CK: Resource Development через захват домена (T1584.001, Domains) → Initial Access через компрометацию цепочки поставок npm (T1195.002) → Execution через JavaScript (T1059.007) → обфускация payload (T1027, Obfuscated Files or Information) → эксфильтрация через C2 (T1105, Ingress Tool Transfer). Стоимость входа для атакующего минимальна, а blast radius определяется популярностью скомпрометированного пакета.

Kill chain: от whois до публикации вредоносного пакета​

Разведка: поиск мёртвых доменов в дереве зависимостей​

[Применимо: внутренний аудит, white box, dependency review]

Работает если: мейнтейнер указал реальный email при регистрации npm-аккаунта, домен публично резолвится через whois. Не работает если: мейнтейнер использует приватный email, GitHub noreply-адрес; TLD не подлежит свободной перерегистрации (.gov, .mil, .edu); registry скрывает email мейнтейнеров.

ОС: Linux или macOS (whois на Windows требует WSL). RAM: 512 МБ. Сеть: online.

npm registry возвращает email мейнтейнеров через npm view <package> maintainers. Задача - вытянуть домены из этих адресов и проверить статус. Пакеты, которые давно не обновлялись, становятся приоритетными целями: чем дольше неактивность, тем выше вероятность, что мейнтейнер потерял контроль над доменом.
Bash:
# Домены мейнтейнеров прямых зависимостей + проверка expiry
for pkg in $(jq -r '.dependencies | keys[]' package.json); do
  npm view "$pkg" maintainers --json 2>/dev/null \
    | jq -r 'if type=="array" then .[].email else .email end' 2>/dev/null
done | cut -d@ -f2 | sort -u | while read domain; do
  exp=$(whois "$domain" 2>/dev/null | grep -i 'expir' | head -1)
  echo "$domain | $exp"
done
Домены в статусах pendingDelete, redemptionPeriod или с просроченной датой - критические находки: их может перерегистрировать любой, и это открывает путь к захвату домена мейнтейнера npm. Скрипт проверяет только прямые зависимости - для полного дерева замените jq -r '.dependencies | keys[]' package.json на npm ls --all --json | jq -r '.. | .resolved? // empty' и извлекайте scoped-пакеты отдельно.

Захват домена и восстановление npm-аккаунта

Предусловия для успешной атаки: домен реально истёк и доступен для регистрации; npm-аккаунт не защищён аппаратным MFA (FIDO2/WebAuthn); email на этом домене используется как основной или recovery для npm.

После регистрации домена атакующий поднимает MX-запись (хватит postfix или любого минимального MTA), жмёт «Forgot password» на npm и получает полный доступ к аккаунту. Классический Resource Development (T1584.001, Domains). Если на аккаунте настроен TOTP, но email используется как канал восстановления - MFA обходится через recovery flow. Красиво, правда?

В npm-инцидентах 2025 года атакующие пошли дальше и использовали фишинг: фейковый домен npmjs.help, по данным Panther и Cymulate, имитировал npm support и перехватывал TOTP-коды в реальном времени. Результат - компрометация аккаунта мейнтейнера 18 пакетов, включая chalk и debug.

Отдельная история - инцидент с node-ipc (CVE-2022-23812, март 2022, CVSS 9.8). Тут не было никакого захвата: сам мейнтейнер (RIAEvangelist) внедрил деструктивный код в версии >=10.1.1 и <10.1.3, перезаписывавший файлы пользователей с IP из России и Беларуси символом сердца. Protestware, а не классический supply chain attack - но паттерн функционально тот же: доверие к publish-правам оказалось единственным барьером, и когда оно упало, downstream-проекты посыпались.

Публикация вредоносной версии: стратегия blast radius​

Предусловия у жертвы (downstream-проект): зависимости с semver-диапазонами (^, ~) вместо exact pin; CI/CD использует npm install вместо npm ci; отсутствует private registry с cooldown-периодом на новые версии.

По данным NVD (CVE-2022-23812), вредоносный код содержался в версиях node-ipc >=10.1.1 и <10.1.3, перезаписывая файлы символом сердца, а начиная с 11.0.0 node-ipc импортировал пакет peacenotwar с потенциально нежелательным поведением. Node-ipc - штука популярная, отсюда и масштаб blast radius.

Эволюция payload: от postinstall до червя Shai-Hulud​

Lifecycle hooks: классический вектор атаки через npm registry​

Самый прямолинейный способ запустить вредоносный код - lifecycle scripts в package.json: preinstall, install, postinstall. По данным Splunk, Node.js выполняет эти скрипты автоматически с привилегиями пользователя, делающего npm install. preinstall срабатывает первым (до резолвинга зависимостей), postinstall - после полной установки. Атакующие традиционно предпочитали postinstall: на этом этапе доступно полное дерево зависимостей.

Когда техника НЕ работает: флаг --ignore-scripts полностью блокирует lifecycle hooks. Сканеры (Socket, Snyk, npm audit) приоритетно анализируют эти скрипты. В случае node-ipc атакующие отказались от lifecycle hooks - по данным StepSecurity, в package.json нет ни preinstall, ни postinstall. Целенаправленный evasion: payload невидим для инструментов, проверяющих только hook-скрипты.

IIFE-инъекция в CommonJS bundle: гипотетический сценарий на основе реальных техник​

Примечание: Ниже - гипотетический сценарий IIFE-инъекции, иллюстрирующий эволюцию техник supply chain атак. Он не описывает реальный инцидент node-ipc (CVE-2022-23812), который был protestware-кейсом - см. раздел выше.
IIFE-инъекция в CommonJS bundle - следующий шаг по сравнению с lifecycle hooks. Атакующий дописывает обфусцированный JavaScript в конец .cjs-файла: IIFE (Immediately Invoked Function Expression) после финального module.exports. Node.js выполняет все top-level выражения CommonJS-модуля при require(), поэтому IIFE срабатывает при каждом импорте - без вызова метода, без конфигурации. Тихо и незаметно.

Гипотетический payload такого типа может реализовать полную цепочку credential theft:
  • Декодирование C2-конфигурации через кастомный шифр
  • Сбор credentials: AWS, Azure, GCP, SSH-ключи, Kubernetes-токены, GitHub CLI конфиги, Terraform state, shell history
  • Компрессия и эксфильтрация на C2 (T1105)
  • Обфускация через obfuscator.io-подобные техники (T1027)
Возможен и targeting gate: хеш пути установки сравнивается с зашитым значением, и если хеш не совпадает - payload молча завершается. Для defender это головная боль: невозможно определить цель без полного перебора пространства путей.

ESM-точки входа при такой атаке остаются чистыми - проекты с import через бандлер, резолвящий поле "module", не затрагиваются. Уязвимость бьёт только по CommonJS-импорту через require().

Shai-Hulud: самопропагация через украденные токены​

Shai-Hulud (сентябрь 2025) - первый вредоносный npm-пакет с функцией червя. По данным Unit 42 и Trend Micro, атака началась с фишингового письма, имитирующего npm security alert - классический трюк с запросом «обновить MFA». После компрометации аккаунта payload разворачивал полную kill chain:
  1. Сканирование .npmrc и переменных окружения → кража npm-токенов и GitHub PAT
  2. Через GitHub API - список всех репозиториев жертвы, включая приватные
  3. Клонирование репозиториев, создание ветки shai-hulud с вредоносным GitHub Actions workflow
  4. Запуск TruffleHog для поиска дополнительных секретов в файловой системе
  5. Автоматическая публикация заражённых версий всех доступных npm-пакетов через украденный токен
Один скомпрометированный аккаунт - и червь расползается по десяткам пакетов без участия человека.

По неподтверждённым данным, масштаб Shai-Hulud 2.0 (ноябрь 2025) вырос на порядок - предположительно тысячи вредоносных GitHub-репозиториев. Ключевые изменения V2:
  • Preinstall вместо postinstall: payload запускается до завершения установки. Даже при неудачной установке код уже выполнен - поезд ушёл
  • Dead man's switch: если кража credentials невозможна, червь пытается уничтожить домашнюю директорию через перезапись и удаление файлов. Если не можешь украсть - сожги
  • Bun runtime: payload-файлы setup_bun.js и bun_environment.js маскируются под установщик Bun, выполнение идёт в Bun вместо Node.js. По данным Panther со ссылкой на Check Point Research, «большинство sandbox-инструментов оптимизировано для Node.js, что делает Bun привлекательным для обхода детекции». Хитро
  • Persistence через GitHub Actions: регистрация заражённой машины как self-hosted runner с workflow discussion.yaml, позволяющим выполнять произвольные команды через создание GitHub Discussions
В дальнейшем аналогичные паттерны кражи токенов и автоматического переиздания пакетов предположительно могут использоваться для компрометации популярных пакетов. Гипотетические сценарии расширения - компрометация CI/CD pipeline через незащищённые pre-production ветки в GitHub, обходя code review.

ОС: Linux/macOS. RAM: 512 МБ. Сеть: online.

Помимо скрипта проверки доменов мейнтейнеров из раздела выше, запускайте:

Проверка npm-подписей: npm audit signatures верифицирует ECDSA-подписи пакетов. Ограничение: подпись валидна при публикации через скомпрометированный, но авторизованный аккаунт - подпись не отличает легитимную публикацию от вредоносной. Это как проверять паспорт у вора, который украл настоящий паспорт.

Поиск артефактов Shai-Hulud: по данным Panther, проверяйте наличие setup_bun.js, bun_environment.js в node_modules; GitHub-репозиториев с именем «Shai-Hulud»; workflow-файлов shai-hulud-workflow.yml в .github/workflows/; lockfile ~/.config/.miasma/run/node.lock (возможный артефакт последующих кампаний, требует проверки).

Grep-паттерны для IIFE-инъекций в CJS-файлах - по рекомендациям Cymulate: Object.defineProperty(.*"(responseText|response)") (перехват HTTP-ответов), window\.ethereum\.(request|send) (hooking криптокошельков), непропорционально большие .cjs (дельта >50 КБ между версиями - сигнал инъекции).

Мониторинг cooldown: новые версии давно не обновлявшихся пакетов заслуживают ручной проверки. Длительный gap между релизами перед внезапным обновлением - характерный маркер компрометации.

Митигация: hardening npm pipeline​

📚 Часть контента скрыта. Этот материал доступен участникам сообщества с рангом One Level или выше
Получить доступ просто — достаточно зарегистрироваться и проявить активность на форуме

в runtime, и ignore-scripts тут бессилен.

Private registry, cooldown и namespace shadowing​

Проксирование через Nexus, Artifactory или Verdaccio позволяет: кэшировать пакеты и контролировать, какие версии попадают в проект; задавать cooldown-период на новые версии (по рекомендации Unit 42 - период карантина перед допуском в прод); блокировать пакеты без provenance-attestation. Namespace shadowing в private registry предотвращает dependency confusion атаку - сценарий, когда внутренний scoped-пакет подменяется одноимённым из публичного npm registry.

Payload node-ipc обращался к sh.azurestaticprovider.net, Shai-Hulud V1 - к webhook-эндпоинтам для эксфильтрации. Жёсткая egress-фильтрация блокирует C2-коммуникацию даже при успешном выполнении payload.

Ограничение: Shai-Hulud 2.0 использовал легитимную GitHub-инфраструктуру (Actions API, self-hosted runners) для persistence и C2. Egress-фильтрация не поймает трафик к api.github.com с валидным токеном - он неотличим от нормальной работы. Атаки через скомпрометированный CI/CD pipeline могут использовать NPM_TOKEN и GITHUB_TOKEN из переменных CI-раннера, и egress к registry.npmjs.org с авторизованным токеном выглядит как обычная публикация.

Когда стандартные защиты недостаточны​

МераЧто блокируетЧто НЕ блокирует
ignore-scripts=truepreinstall/postinstall hooksIIFE в CJS bundle (node-ipc)
Lockfile + npm ciАвтообновление до новой версииКомпрометацию зафиксированной версии после обновления lockfile
npm audit signaturesПакеты без ECDSA-подписиПакеты, подписанные через украденный токен
SLSA ProvenanceПубликации без верифицированного CI/CDАтаки через скомпрометированный CI/CD pipeline
Private registry + cooldownМгновенное подтягивание свежих вредоносных версийПакеты, прошедшие cooldown-период
Egress filteringОбращения к внешним C2Эксфильтрацию через GitHub API и npm registry

SLSA provenance - отдельная боль, на которую указывает Unit 42: по непроверенным данным, атакующие могут пушить в незащищённые pre-production ветки, что запускает GitHub Actions и генерирует валидный провенанс для троянизированных пакетов. Провенанс бесполезен, если скомпрометирован сам pipeline, генерирующий аттестацию.

Рабочий подход - defense in depth: lockfile + ignore-scripts + private registry с cooldown + egress filtering + регулярный аудит доменов мейнтейнеров. Каждый слой закрывает слепое пятно предыдущего. Ни один слой не работает в одиночку.

Три года я разбираю supply chain инциденты в npm - и вижу одну и ту же закономерность: индустрия вкладывается в detection вредоносного кода, игнорируя атаку на identity мейнтейнера. Socket, Snyk, npm audit, YARA-правила - всё это ловит известные паттерны: lifecycle hooks, обфускацию, сетевые вызовы. Но когда атакующий получает контроль над легитимным аккаунтом через просроченный домен, его публикация неотличима от обычного обновления до первого срабатывания payload. Вся модель безопасности npm стоит на одном предположении: мейнтейнер - доверенная сторона. Стоит этому предположению рухнуть, и все остальные слои работают в режиме догоняющего.

npm ввёл обязательный 2FA для популярных пакетов - но TOTP не спасает, если email служит каналом recovery. Аппаратные ключи (FIDO2/WebAuthn) закрывают вектор, но их проникновение среди open source мейнтейнеров минимально. А рост wormable атак - Shai-Hulud и возможные потомки - означает, что компрометация одного аккаунта автоматически масштабируется на десятки пакетов без человека в цепочке. На мой взгляд, через год-два мы увидим обязательную привязку npm-публикации к hardware MFA и верифицированному CI/CD pipeline. Но до тех пор единственный реалистичный подход - считать каждую зависимость потенциально скомпрометированной и строить pipeline, способный выжить при предательстве любого одного пакета в дереве. Запустите скрипт проверки доменов из второго раздела на своём package.json прямо сейчас - результат может неприятно удивить.
 
Мы в соцсетях:

Взломай свой первый сервер и прокачай скилл — Начни игру на HackerLab

🚀 Первый раз на Codeby?
Гайд для новичков: что делать в первые 15 минут, ключевые разделы, правила
Начать здесь →
🧭 Навигатор · ИБ 2026
Не знаешь, какой трек твой?
5 направлений ИБ, реальные зарплаты и точка входа для каждого — в одном треде.
JuniorSenior+
100K → 600K+ ₽ /мес
Открыть навигатор →
🔴 Свежие CVE, 0-day и инциденты
То, о чём ChatGPT ещё не знает — обсуждаем в реальном времени
Threat Intel →
💼 Вакансии и заказы в ИБ
Pentest, SOC, DevSecOps, bug bounty — работа и проекты от проверенных компаний
Карьера в ИБ →

HackerLab