Безопасность npm-проектов, часть 1
Всем привет! В прошлом посте мы начали рассматривать важный вопрос о безопасности в npm и поговорили о том, какие меры использует сама компания для выявления и предотвращения угроз. В этот раз я хочу поговорить об инструментах и подходах, доступных лично вам, и которые я настоятельно рекомендую внедрить в свою практику всем разработчикам.
Команда npm install
поддерживает ряд скриптов (например: preinstall
, install
/postinstall
), которые могут выполняться в процессе установки пакета. При этом содержимое таких скриптов ничем не ограничивается и выполняется от лица пользователя, выполняющего установку. Такие скрипты являются одним из основных способов для злоумышленников влезть в компьютер, на котором устанавливается вредоносный пакет, и похитить важную информацию.
Пример пакета, содержащего вредоносный скрипт:
Если пользователь выполнит команду установки пакета из примера выше: npm install malicious-package
, то это приведет к выполнению скрипта evil.sh, который потенциально может совершить любые действия от лица текущего unix-пользователя.
Возможно, вы слышали, что в середине 2018 года произошел громкий инцидент с пакетамиeslint-scope
иeslint-config-eslint
, когда злоумышленник смог получить к ним доступ и опубликовать новую версию пакетов, содержащую вредоносный код в скриптах установки.Таким образом, пользователи, которые устанавливали один из этих пакетов, незаметно для себя отправляли собственные токены аутентификации злоумышленнику, который мог использовать их в дальнейшем для получения доступа к закрытому коду и информации, а также для дальнейшего распространения вируса.
Чтобы изолировать проблему, команде npm пришлось отозвать все токены npm, которые были сгенерированы до даты начала атаки. Это привело к тому, что npm registry начал испытывать проблемы с доступностью, потому что пользователи начали массово перевыпускать токены, не говоря о том, что это нарушило работу огромного количества автоматизированных конвейеров, которые обращались к npm с использованием токенов.
Интересно, что код злоумышленника был написан не очень грамотно, поэтому он не всегда корректно выполнялся, к счастью, это ограничило эффективность атаки. Если бы злоумышленник имел более высокую квалификацию и написал бы полноценный вирус, работающий в полностью автоматическом режиме, то масштаб атаки и последствия могли бы быть намного существеннее.
Слава богу, в настоящее время npm на своей стороне выполняет статический и поведенческий анализ всех пакетов, публикуемых в npm с целью выявления подобных атак, что должно существенно ограничивать эту угрозу. Однако не стоит полностью списывать ее со счетов.
Если у вас есть существенные причины не доверять устанавливаемому пакету, то вы можете запретить выполнение скриптов установки при помощи следующей команды:
npm install suspicious-package --ignore-scripts
Также, вы можете полностью запретить выполнение скриптов установки при помощи следующей команды (или добавить настройку в .npmrc
):
npm config set ignore-scripts true
Но учтите, что некоторые пакеты используют скрипты установки для собственной инициализации и не будут корректно работать при установке с запретом на запуск скриптов. Такую практику стоит использовать только в том случае, если у вас есть очень серьезные на то причины.
Чтобы дополнительно обезопасить себя от подобных атак, постарайтесь максимально ограничить среду, в которой выполняются команды npm, такие как npm install
. Как вариант, их можно выполнять внутри Docker-контейнера, на виртуальной машине или в любой другой песочнице с ограниченным доступом. Разумеется, никогда не стоит запускать npm от лица root-пользователя; наоборот, лучше запускать эти команды от лица пользователя с минимальным доступом и набором прав. Антивирусы и брандмауэры также могут сократить риски. Принцип прост: чем меньше полномочий получит процесс npm, тем безопаснее будет его работа.
Также полезно будет настроить систему предупреждений: если вдруг процесс начнет совершать какие-то подозрительные действия, то вам следует об этом знать. Выявление угрозы безопасности поможет не только вам и вашей команде или компании, но и всему сообществу в целом. Поэтому не забудьте составить и отправить отчет о найденной уязвимости в npm.
Первое правило при работе с ключами доступа и токенами: никогда не храните их в открытом доступе, если этого можно избежать. К примеру, не стоит хранить токен npm в файле .npmrc
на вашей машине, если можно аутентифицировать в интерактивном режиме.
Если вам всё же необходимо использовать токен аутентификации npm в каком-то автоматическом конвейере (например, для CI/CD), то ограничьте его полномочия. Например, если токен нужен только для установки пакетов из приватного репозитория, то создавайте его в режиме read-only, чтобы в случае его утечки злоумышленник не мог отправить вредоносный код в ваш репозиторий. Это позволит ограничить масштаб угрозы.
Также хорошей практикой является привязка токена аутентификации npm к определенному диапазону IP-адресов (CIDR). Закажите себе статический IP (или серию IP) и привяжите его к своему серверу. Как правило, любой облачный провайдер позволяет это сделать, даже если ваш сервер запускается по запросу (on-demand). Таким образом, если злоумышленник сможет украсть токен, то он не сможет использовать его с другой машины.
Не размещайте токен аутентификации в файле .npmrc
, и тем более не добавляйте его в систему контроля версий (Git). Вместо этого используйте переменную окружения NPM_TOKEN
в связке со специальным защищенным хранилищем для токенов (изучите документацию вашего провайдера).
Сгенерировать новый токен можно при помощи команды npm token create
, которая опционально принимает следующие аргументы:
--read-only
— создаёт токен, который позволяет только считывать данные из репозитория (т. е. устанавливать пакеты, но не публиковать их);--cidr=
— создаёт токен, который позволяет аутентифицироваться только хостам в указанном диапазоне IP. Рекомендую использовать калькулятор CIDR, чтобы быть уверенными в корректности задания диапазона IP. Пример:--cidr=192.0.2.0/24
.
Также рекомендуется регулярно проводить аудит всех токенов, привязанных к npm-аккаунту, и отзывать ненужные. Посмотреть список всех токенов можно в личном кабинете на сайте npm, либо при помощи команды npm token list
.
Учтите, что аутентификация при помощи команды npm adduser
приравнивается к созданию токена и сохранению его в файле .npmrc
в домашней директории пользователя. Удаление же токена приведет к разлогиниванию пользователя.
Аутентификация в файле ~/.npmrc
выглядит следующим образом:
//registry.npmjs.org/:_authToken=00000000-0000-0000-0000-000000000000
Если вы используете различные независимые npm registry, то таких строк может быть несколько.
Убедитесь, что содержимое файла ~/.npmrc
доступно только вашему unix-пользователю!
Что касается закрытых ключей шифрования (например, для SSH), то убедитесь, что они защищены сильным паролем и используют надежный алгоритм шифрования. В случае компрометации такого ключа злоумышленник не сможет им воспользоваться, т. к. не знает пароль, а методы математического взлома окажутся неэффективными. В случае с SSH, в настоящее время алгоритм EdDSA
считается наиболее надежным. Однако стоит учесть, что старые системы могут его не поддерживать. Тогда используйте два ключа: Ed25519
(EdDSA
) для всех совместимых систем и RSA
(с минимум 2048 битным ключом) для устаревших систем.
Одним из старейших приемов в рукаве злоумышленников является использование названий, похожих на оригинальные, но отличающихся одним или несколькими символами. Пользователи часто совершают опечатки при вводе тех или иных названий. Особенно это критично при вызове команды, например, npm install packae-name
: в лучшем случае это приведет к ошибке 404, а в худшем — может вызвать вредоносный код.
Команда npm старается автоматически вычислять попытки подобного подлога и блокировать пакеты, которые выдают себя за другие. Вам же, как пользователю, следует просто внимательнее относиться к вводу названий пакетов с клавиатуры, по возможности пользуйтесь функцией copy-and-paste.
Про правила использования безопасных паролей сказано уже очень много, не вижу смысла повторяться, однако отмечу основные моменты:
- используйте уникальные длинные пароли с буквами в разном регистре, цифрами и спец. символами (16 символов будет достаточно);
- не используйте один и тот же пароль более чем в одном месте. Для каждого ресурса создавайте новый пароль;
- храните пароли только в защищенном месте и передавайте только по зашифрованному соединению;
- у каждого пользователя должен быть свой именной логин и пароль, не используйте одни и те же реквизиты более чем для одного человека.
Для решения всех вышеописанных вопросов стоит использовать специальное ПО: менеджер паролей. Я использую открытый KeePass (есть версии практически для любой платформы). Базу данных паролей стоит защитить сложным мастер-паролем, который вам необходимо запомнить. Дополнительно можно использовать файл-ключ, который хранится, например, на флешке. Сам же файл базы данных можно положить в любое облачное хранилище (Яндекс.Диск, Google Drive или DropBox), чтобы иметь к нему синхронизированный доступ сразу со всех устройств.
Мой коллега недавно перевел статью, в которой подробнее разбирается вопрос оптимальной длины пароля, рекомендую ознакомиться!
Механизмы мультифакторной аутентификации значительно повышают безопасность вашей учетной записи, т. к. создают дополнительный барьер на пути злоумышленников: даже если кому-то удастся взломать или перехватить ваш пароль, то обойти дополнительный фактор будет гораздо сложнее. При этом стоит обратить внимание именно на использование программ-аутентификаторов и не использовать SMS-сообщения в качестве доп. фактора, т. к. их гораздо проще перехватить.
К счастью, npm также поддерживает двухфакторную аутентификацию при помощи программ-аутентификаторов. Она работает в двух режимах: «только аутентификация» или «аутентификация и запись». Лучше всего использовать режим «аутентификация и запись», т. к. это гарантирует самый высокий уровень безопасности, потому что npm будет просить вас ввести токен OTP не только при вызове команды npm adduser
, но и при любой попытке записать что-то в реестр (например, при вызове npm publish
).
О том, как включить npm 2FA, написано подробно в официальной документации. Процесс в целом ничем не отличается от стандартного: вам нужно выбрать режим работы, а затем просканировать QR-код, который будет показан на экране при помощи выбранной вами программы-аутентификатора. Сделать это можно как через личный кабинет на официальном сайте, так и через CLI при помощи команд:
npm profile enable-2fa auth-and-writes
— режим «аутентификация и запись» (рекомендуется)npm profile enable-2fa auth-only
— режим «только аутентификация»
При использовании CLI npm попросит вас ввести пароль от аккаунта (даже если вы уже аутентифицированы), а затем покажет QR-код. После сканирования кода нужно ввести одноразовый пароль (OTP), который покажет приложение на устройстве, чтобы подтвердить успешность процедуры.
При привязке аутентификатора npm также выдаст вам набор одноразовых кодов восстановления, их необходимо сохранить в надежном месте, на случай, если придется восстанавливать доступ к аккаунту при утере аутентификатора.
Если утеря всё же произошла, вы можете ввести неиспользованный ранее код восстановления вместо одноразового пароля при аутентификации, а затем сбросить 2FA командой npm profile disable-2fa
, введя затем еще один код восстановления. Если же вы потеряли и аутентификатор, и коды восстановления, то вам придется обратиться в службу поддержки npm (надеюсь, они защищены от социальной инженерии).
По умолчанию, когда вы выполняете команду npm publish
, npm собирает в архив все файлы в директории пакета за исключением некоторых.
.git
CVS
.svn
.hg
.lock-wscript
.wafpickle-N
.*.swp
.DS_Store
._*
npm-debug.log
.npmrc
node_modules
config.gypi
*.orig
package-lock.json
Часто из-за этого в registry утекают файлы, которые не должны быть опубликованы, например, файлы с паролями. Это серьезная угроза безопасности. Если вы случайно отправили в реестр npm какие-то чувствительные файлы, то вам придется не только удалить архив пакета из реестра, но и полностью заменить все скомпрометированные данные.
А чтобы обезопасить себя от подобных утечек, я рекомендую всегда использовать поле files
в файле package.json
. Оно представляет собой массив, содержащий перечисление файлов (можно использовать шаблоны glob), которые должны попасть в архив собранного пакета. Такой явный подход гарантирует, что в публичный доступ случайно не утекут какие-то нежелательные файлы.
Нужно заметить, что следующие файлы всегда попадают в архив, даже если не перечислены в массиве files
:
package.json
README
CHANGES
,CHANGELOG
,HISTORY
LICENSE
,LICENCE
NOTICE
- Файл, указанный в поле
main
При этом файлы README
, CHANGES
, LICENSE
и NOTICE
могут иметь любой регистр символов в названии и расширение.
По этой причине важно, чтобы перечисленные выше файлы никогда не содержали чувствительной информации.
Чтобы проверить, что же попадет в архив при запуске команды npm publish
, можно использовать флаг --dry-run
либо команду npm pack --dry-run
. Вторая команда, в отличие от первой, пропустит скрипты препаблишинга и сразу попытается собрать архив. Аргумент --dry-run
гарантирует, что изменения будут произведены в тестовом режиме только на вашей машине, пакет не будет никуда отправляться, и архив не будет реально создан в файловой системе.
В этом посте мы начали рассматривать инструменты и подходы, которыми каждый из нас может пользоваться, чтобы повысить безопасность своего npm-проекта. Я настоятельно рекомендую всегда иметь эти аспекты в виду и внедрить описанные инструменты у себя — в наше сложное время дополнительные меры защиты никогда не будут лишними.
В следующем посте я хочу снова вернуться к вопросам безопасности и рассмотреть остальные инструменты, о которых я не успел упомянуть сегодня.
Если вам понравился материал, то, пожалуйста, ставьте лайки, подписывайтесь на наш блог и делитесь ссылками с коллегами. Так мы будем понимать, что наша работа востребована и продолжим радовать вас новыми полезными материалами.
Если же у вас есть вопросы или желание что-то добавить по теме, то не стесняйтесь оставлять комментарии, я с радостью приму участие в обсуждении и учту ваши пожелания в следующих постах.
Все изображения в статье являются собственностью своих авторов или законных правообладателей и используются исключительно в некоммерческих образовательных целях.