[] 12 странных вещей, которые могут произойти после установки npm пакета

в 9:11, , рубрики: javascript, npm, security, информационная безопасность

Пару месяцев назад я начал заниматься проектом под названием malicious-packages (ака "вредоносные пакеты"). Он следит за обновлениями в npm репозитории, скачивает все новые модули, а затем проверяет их на вшивость — ищет сетевую активность, подозрительные операции с файловой системой и т.д. Даже маленькие проекты на node.js часто имеют большое дерево зависимостей, и у разработчиков физически нет возможности проверить их все. Это даёт злоумышленникам огромный простор для манёвра, и возникает вопрос — сколько же всякой гадости прячется по тёмным углам npm registry? 180000 проверенных пакетов спустя я получил примерный ответ.

image

И этот ответ — пожалуй, не так уж и много.
[прим.: на medium есть англоязычная версия этой статьи, также за моим авторством]

Что npm пакет может сделать c вашей системой?

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

NPM скрипты позволяют пакетам выполнять произвольные команды в момент установки и удаления. К ним относятся preinstall, install, postinstall, preuninstall и postuninstall, которые автоматически выполняются npm в соответствующий момент жизненного цикла пакета. Что они могут сделать? Всё то же самое, что может сделать ваш текущий пользователь — например удалить все ваши фотографии с последнего отпуска, или слить историю вашего браузера в ФБР (хотя, скорее всего, она и так у них есть). Это поведение можно отключить передав флаг --ignore-scripts, но, во-первых, это никто не делает, а во-вторых — таким образом можно сломать кучу вполне себе благонадёжных пакетов. Именно через скрипты была осуществлена нашумевшая атака на ESLint, которая затронула пользователей eslint-scope (6 миллионов установок в неделю) и eslint-config-eslint (2 тысячи установок в неделю).

Пакет получает второй шанс усложнить вам жизнь при инициализации (обычно происходит при первом вызове require). Теперь у него появляется возможность модифицировать глобальные переменные и другие пакеты, чтобы, например, украсть приватный ключ от вашего биткоин-кошелька, или сделать метод crypto.randomBytes не таким уж и случайным.

image

Сколько вредоносных пакетов было обнаружено?

Что ж, список нельзя назвать впечатляющим, всего было найдено 3 пакета, которые к настоящему моменту удалены из npm репозитория стараниями npm security team. Давайте пройдёмся по нему:

  • commander-js пытается маскироваться под настоящий commander.js (который https://www.npmjs.com/package/commander), но содержит одну необычную деталь, а именно postinstall скрипт, скачивающий и выполняющий содержимое http://23.94.46.191/update.json (на момент публикации не содержит ничего криминального). Security advisory: https://www.npmjs.com/advisories/763.
  • rrgod через всё те же скрипты загружает и выполняет скрипт из http://static.ricterz.me, который, в свою очередь, пытается загрузить ещё один скрипт, в настоящее время недоступный. Security advisory: https://www.npmjs.com/advisories/764.
  • portionfatty12 нельзя назвать полностью вредоносным, однако способ, которым автор собирает статистику об установках своего детища весьма сомнителен — это отправка вашего публичного ssh-ключа (~/.ssh/id_rsa.pub) на сервер, который в настоящий момент уже недоступен. Security advisory: https://www.npmjs.com/advisories/765.

Несмотря на весьма скромные результаты, в процессе анализа всех подозрительных пакетов (а я просмотрел более 3000 отчётов, чтобы найти эти три жемчужины) обнаружилось немало забавных и не очень вещей, о которых вы обычно не задумываетесь (или тщательно стараетесь не думать) набирая npm install. Итак, давайте представим, что вы случайно выбрали один из множества пакетов из репозитория и устанавливаете его. Что может пойти не так?

1. Пакет может переопределять методы в других пакетах (в том числе и из стандартной поставки Node.js)

Впрочем, если вы прочитали пару предыдущих абзацев или работаете с экосистемой javascript больше недели, то вряд ли для вас это будет являться новостью. А вот масштабы этой катастрофы вполне могли от вас ускользнуть. Так, если в вашем проекте есть хотя бы несколько зависимостей, то скорее всего метод fs.closeSync у вас уже переопределён (и, может быть, не один раз). Огромное количество пакетов модифицирует чужое API, но лишь немногие из них имеют хоть сколько-нибудь вескую причину для этого. Среди "рекордсменов" — graceful-fs с 12 миллионами установок в неделю, который переопределяет десяток методов из fs. Так же стоит отметить async-listener, переопределяющий 46 различных методов, включая злополучный crypto.randomBytes, что меня несколько напрягло, когда я впервые это обнаружил.

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

2. Пакет может определять изменённое API вносить в него дополнительные исправления

image

Да, некоторые пакеты так и поступают (чаще всего в отношении того же graceful-fs) используя чудеса акробатики вроде /graceful-fs/.test(fs.closeSync.toString()). Так что, если вы вдруг столкнулись с непонятными проблемами в стандартной библиотеке, попробуйте просто установить парочку случайных npm пакетов. Или просто выключите компьютер и прогуляйтесь по ближайшему парку, жизнь слишком коротка, чтобы разбираться во всём этом.

3. Он может отправлять аналитику

Adblock вас не защитит, если дело происходит в консоли, и авторы некоторых пакетов успешно этим пользуются. Некоторые отправляют самую базовую информацию, как, например, ecdsa-csr:

// POST https://api.therootcompany.com/api/therootcompany.com/public/ping
{
  "package":"ecdsa-csr",
  "version":"1.1.1",
  "node":"v10.14.2",
  "arch":"x64",
  "platform":"linux",
  "release":"4.9.125-linuxkit",
  "action":"install",
  "ppid":"eDSeYr9XUNRi9WhWli5smBNAvdw="
}

Некоторые не так стесняются. Вот, например часть отчёта serverless (оригинал раза в 2 больше):

// POST https://tracking.serverlessteam.com/v1/track
{
  "userId":"0e32cba0-14ef-11e9-9f89-b7ed4ca5dbba",
  "event":"framework_stat",
  "properties":{
    "version":2,
    "general":{
      "userId":"0e32cba0-14ef-11e9-9f89-b7ed4ca5dbba",
      "context":"install",
      "timestamp":1547135257977,
      "timezone":"GMT+0000",
      "operatingSystem":"linux",
      "userAgent":"cli",
      "serverlessVersion":"1.35.1",
      "nodeJsVersion":"v10.14.2",
      "isDockerContainer":true,
      "isCISystem":false,
      "ciSystem":null
    }
  }
}

К счастью, jquery никакой статистики не отправляет, так что его по-прежнему можно устанавливать в тайне ото всех. До поры до времени.

4. Ваш компьютер может использоваться вместо CI/CD сервера

Зачем вам компилировать ваш пакет, если это могут сделать ваши пользователи при установке? Можно получить дополнительную ачивку, если указать только мажорную версию требуемого компилятора, например typescript@3. Одна надежда на грамотных ребят из Microsoft, которые умеют делать правильный semver.

Можно ли зайти ещё дальше? Конечно!

"postinstall": "eslint --ext .js,.vue --fix src"

Теперь можно спать спокойно — все пользователи получат идеально отформатированные исходники вашего пакета.

5. Он может попытаться напугать вас

image

Если вы посмотрели все серии Mr. Robot, но всё ещё недостаточно замотивированы, чтобы прочитать Computer Networks и сделать что-нибудь действительно впечатляющее, то есть простое решение — продемонстрируйте миру свои умения посредством пары строчек в postinstall скрипте. Именно так и поступил автор pizza-pasta (а заодно подарил мне КДПВ).

{
  "name": "pizza-pasta",
  "author": "Zeavo",
  "scripts": {
    "install": "mkdir -p ~/Desktop/hacked && touch ~/Desktop/hacked/pwnddddd && wget https://imgur.com/download/KTDNt5I -P ~/Desktop/hacked/",
    "postinstall": "find ~/.ssh | xargs cat || true && printf 'nnnnnnOH HEY LOOK SSH KEYSnnnnHappy Birthday! Youve been h4ck0rednnn'"
  }
}

6. Пакет может загружать и исполнять bash-скрипты

Слышали ли вы, что делать curl|bash не очень хорошая идея? Если нет, то вы вполне можете оказаться сотрудником ORESoftware, у которых есть целая куча пакетов с подобными строчками в postinstall скрипте:

curl --silent -o- https://raw.githubusercontent.com/oresoftware/realpath/master/assets/install.sh | bash

Пока что в этом скрипте нет ничего криминального… Пока. Надеюсь, что все, у кого есть доступ к master в oresoftware/realpath, исключительно честные и порядочные люди.

7. У вас могут попросить пароль

Автор magicleap придумал достаточно необычный способ распространять приватный пакет через публичный репозиторий. Его проект состоит из зашифрованного архива и утилит для его расшифровки — но только если вы поместите правильный ключ в переменную окружения MAGICLEAP:

// Оригинал: https://github.com/modulesio/magicleap/blob/master/decrypt.js
var key = process.env['MAGICLEAP'];
console.warn('Decrypting magicleap module with MAGICLEAP environment variable');
const ws = fs.createReadStream(path.join(__dirname, 'lib.zip.enc'))
  .pipe(crypto.createDecipher('aes-256-cbc', Buffer.from(key, 'base64')))
  .pipe(fs.createWriteStream(path.join(__dirname, 'lib.zip')));

8. Пакет может пропатчить сам себя во время установки

У автора fake-template нет времени, чтобы отделять тесты от непосредственного кода, тем более, что это легко сделать, добавив в конец тестовых строк специальный комментарий:

// Оригинал: https://github.com/framp/fake-template/blob/master/index.js
const template = (string, tag=defaultTag) => {
  if (mode !== 'literal') throw new Error('Invalid template')
  return (context={}) => tag(literals, ...expressions.map(evalInContext(context)))
}

assert.equal(template('')(), ``) // TEST
assert.equal(template('abc')(), `abc`) // TEST
const dog = 'Orlando' // TEST
assert.equal(template('abc ${dog} lol ${cat}')({dog}), `abc ${dog} lol ${cat}`) // TEST

А затем удалить их через sed:

"postinstall": "sed -i '/\/\/ TEST/d' index.js"

Просто и элегантно!

9. Пакет может изменить настройки npm

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

"preinstall": "npm config set package-lock false"

10. Он может изменить фон вашего рабочего стола на фото Николаса Кейджа

image

Вот ссылка, просто на всякий случай — https://www.npmjs.com/package/cage-js. Возможно, стоило и этот пакет зарепортить, как вредоносный.

11. Пакет может вас зарикроллить

Именно этим занимается ember-data-react, открывая знаменитое видео во время установки. К сожалению, никаких данных из Ember в React с его помощью перенести не удастся — в нем нет ни строчки javascript кода.

12. Он может просто не установиться

image
Несуществующие зависимости, неправильно указанные версии, приватные репозитории, канувшие в лету — вы не сможете установить около 0.6% всех пакетов из npm репозитория.

Вместо заключения

NPM пакеты могут делать странные вещи с вашей системой, и у вас не так много вариантов защиты от этого. Используйте package-lock.json, чтобы избежать внезапных обновлений (и следите, чтобы никто его не отключил без вашего ведома), настройте CSP на фронтенде, чтобы бэкдор в стороннем модуле хотя бы не смог слить данные своему автору. И сделайте бэкап ваших фотографий, на всякий случай.

Если вы чувствуете в себе достаточно сил для погружения в чудесный мир npm пакетов — вы можете найти все исходники здесь: https://github.com/malicious-packages/core. Утилита полна хаков и неоптимальных решений, но она вполне справляется со своей задачей. Так же в репозитории есть MongoDB дамп с результатами анализа более 180 000 пакетов, главное, не забудьте добавить фильтр {'reports.status': 'unverified'}. Я не планирую больше развивать этот проект из-за недостатка времени, но постараюсь помочь со всеми вопросами и проблемами, если таковые будут.

Берегите себя и свои приложения!

Автор: Владимир

Источник


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js