Как стать автором
Обновить

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

Почему такой флаг до сих пор не введён — неясно.

npm install --ignore-scripts


Как подобные проблемы решаются в других языках, в частности, в Ruby и Python?

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

Спасибо за наводку, добавил в статью.

Может быть вместо этого проксировать встроенные модули, в хэндлере try {throw} catch, по стэку смотреть, откуда была вызвана функция и либо разрешать и делать что попрошено, либо пробрасывать исключение наверх… Хотя не, производительность просядет, наверное...

Ошибку не обязательно кидать, чтобы получить из неё стек.


new Error('stack trace').stack

Но взятие стека на каждый чих — весьма не быстро, да.

Размазывать права по всему коду приложения?
Более того если один внешний модуль подключается их двух точек вашего приложения? — Что произойдет если они укажут разные права?
Вариантом получше видится вызов библиотеки в одной точке приложения, и там же определить права для всех внешних модулей(ну или вообще в отдельном конфиг-файле)
Ни в коем случае нельзя решать этот вопрос изменением исходных кодов, еще один уровень мета-программирования JS не выдержит.
Решение такое: подменяем require, это не проблема, в отдельном конфиг-файле указываем какие модули мокать, и какие у них права, тут можно создать группы прав.
В идеале, требуемые права должны быть в package.json самого пакета, как у приложений в мобильных сторах.

Еще вариант, уровень доверия пакета определять на основе его использования в крупных репозиториях, а уж крупные компании могут и должны позволить себе аудит безопасности.
В идеале, требуемые права должны быть в package.json самого пакета

Разумеется. Потенциальный злоумышленник пусть сам указывает, какие права ему нужны.

А мы сможем посмотреть и решить, дать или не дать :)
Как по мне, это хорошее решение. Просто потому, что разработчикам пакетов придется оправдываться перед пользователями, зачем им какое-либо разрешение. Если пакет добавляет пробелы в начало строки, а требует доступ в сеть и к файловой системе, то либо он постарается объяснить, зачем ему это, либо те, кого беспокоит безопасность его приложения, найдут другой пакет.

Согласен, это решение лучше, чем текущее, когда у каждого пакета, по сути, есть все права. Но может получиться как с приложениями в Андроиде, когда спустя несколько лет все же добавили возможность приложение поставить, а права не давать.

Да что же все так привязались к бедному npm. Можно подумать, с другими менеджерами пакетов что-то по другому. Вопрос безопасности решается легко — отсматривай, что ты включаешь, и фиксируй версию. Всё. Не надо делать вид, что у вас там чёрный ящик, который надо ограничивать.


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

Лично я привязался к npm исключительно потому, что на NodeJS пишу, а на Python и на Ruby — только читаю.

Nuget — всё то же самое
Composer — всё то же самое
Maven — то же самое
В последнее время всё больше компонентов используется напрямую с гитхаба — тем более всё то же самое. И это только примеры с тех языков, с которыми я непосредственно работал.


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


Более того — даже наличие специальных людей не гарантирует, что в пакете случайно не будет функции, которая разнесёт всю систему (привет яндекс диску, один из релизов которого делал rm /rf).


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


Так что такой подход очень вреден — это иллюзия безопасности вместо осознанного принятия рисков и принятия корректных мер по борьбе с ними.

Нужен CORS для NodeJS :-)

Например, пакету может требоваться доступ к файловой системе для записи логов\картинок\кеша\чего угодно.

Но ведь для этого он будет использовать другую библиотеку
Вы правы, ограничение доступа в ФС и сети по принципу вкл/выкл — это только первый шаг. Не думаю, что так сложно написать доверенный модуль, который бы разрешал доступ к ФС только на чтение/запись и/или в конкретные каталоги. Я не берусь за это по одной простой причине: возможно, это уже сделано, а отягощать базовый paraquire я не хочу. paraquire должен оставаться лёгким и компактным, чтобы его можно было, во-первых, быстро просмотреть, во-вторых, добавляь в репозиторий «намертво», а не через node_modules.

Переменные окружения — это process.env, о котором я говорил в посте. Разрешение на них даётся отдельно от разрешения на сеть и отдельно от разрешения на ФС.
Maven — то же самое

Do tell me all about it. С каких пор мавен при притаскивании зависимостей из репозитория выполняет произвольные команды из их pom'ов?

Проблема не только и не столько в командах (настолько "в лоб" хакер вряд ли будет что-то делать), сколько в содержимом зависимостей, которые могут помимо целевого назначения делать всё что угодно. Как пример — недавняя статья по поводу пакета, который переменные оружения отправлял на сервер хакера. Зависимость из мавена может делать это точно так же. И никто по этому поводу почему-то не переживает.

Только в случае java вам надо явно дёрнуть какой-то класс из вредоносной зависимости. В случае проблемы с npm'овскими хуками — вам не нужно дёргать зависимость, всё на pre_install/post_install случится.


Т. е. для атаки в случае java нужно, чтобы атакуемый сам вызвал вредоносный код (прямо или транзитивно), а в случае npm — достаточно, чтобы вредоносный модуль был в DAGе зависимостей. В общем, поверхность атаки различается значительно.

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


  • потенциально вредоносный код сторонних зависимостей (проблема универсальная, не зависящая от инструментария сборки наблюдается и в java, и в js, и в c++, хоть при использовании централизованных репозиториев, хоть при вендоринге);
  • наличие хуков, позволяющих злоумышленнику выполнить произвольный код в процессе резолва/установки зависимостей (и это проблема инструментаб, в частности, npm).

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


Во-вторых, в этом треде мы обсуждаем вектор атаки, когда вредоносный код активируется в рантайме. Решение автора статьи защищает именно от этого.

Первый коммент видел, но это решение очень условное, если по умолчанию этот флаг не включен. Кроме того, часть пакетов может поломаться из-за его отключения. Даже если они сделают её включенной по умолчанию и добавят whitelist, то из-за привычки к микропакетам в js-мире поддержка такого whitelist'а будет очень негуманной по ресурсам и большинство забьёт.


Ок, если говорить только в контексте активации в рантайме, то я снимаю свои возражения.

Приведу другой пример в дополнению к вашему: очень популярная библиотека quartz до недавнего релиза при запуске проверяла наличие новых версий и сообщала об этом в логе. Обычно это отключалось через system properties, но в некоторых версиях отключение не работало. Если у вас приложение в закрытом контуре, то такое поведение очень неприятно.


В последней версии от этого таки избавились.

А вы часто устанавливаете зависимости, которые потом никак не используете? Если нет, то разницы никакой.

Я — нет. Я наоборот смотрю пристальным взглядом в mvn dependency:tree -Dverbose=true и проверяю корректность дерева, фиксирую версии принесённые транзитивно, когда надо, выкашиваю лишнее из транзитивки. Но это не самая распространенная практика.


При использовании вектора через хуки в фейковую зависимость добавляется реальная и, возможно, реэкспорт методов из реальной библиотеки.


При использовании вектора через передачу управления также можно спроксировать всё в реальную библиотеку ниже по дереву зависимостей.

Думаю, все потому что, что идея микромодулей в основном используется только в npm. В pypi, например, так не очень принято.

Таки большие модули отличаются только тем, что вы сразу отказываетесь от идеи проверять, что там внутри: )


Ну и нет — что джава, что PHP приложения сейчас обычно тоже тащат с собой кучу сторонних модулей. Но про это почему-то никто не пишет.

Таки большие модули отличаются только тем, что вы сразу отказываетесь от идеи проверять, что там внутри: )

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


Ну и да, в силу того, что я не очень дружу с документацией, то часто приходится лезть в код и смотреть :)

Тут вопрос скорее в количестве. Редко какой Python проект тянет больше пары десятков зависимостей. Для node.js из-за очень мелкой разбивки средняя на порядок больше. Ситуация с npm ближе к maven, нежели к pip или gems. И в мире джава проблемой сборки уже давно все интересуются, хотя нормального решения вроде не видно.

Не пишут потому что эта проблема уже давно решена. Ну, не на 100%, вряд ли это вообще возможно, но настолько, насколько это практически важно и можно.

И как это проблема решена?

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


А на уровне менеджера пакетов — какой-нибудь Nexus Pro вполне умеет большинство из того, что тут предлагалось. Просто потому, что история подобного софта намного длиннее чем у NPM. Ну т.е. вы будете иметь всю историю зависимостей проекта, со всеми известными для каждой уязвимостями, вы не сможете (если настроить) подключить к проекту зависимость, про которую заведомо известно, что там дырка, и так далее. Этот модуль Nexus по-моему называется firewall.


А дальше — уже нужны не только и не столько технические решения.

Получится ли у вас в последствии переопределить поведение ES modules (import) как require?
Это ключевое слово, так что вряд ли. Многое зависит от того, будет ли реализация import лежать на v8 (и тогда что-то сделать с ней будет очень проблематично) или же осуществляться силами NodeJS (и тогда есть шанс, что что-то получится). Впрочем, не думаю, что нодовцы отменят require, всё-таки нода не питон, тут так ломать обратную совместимость не принято.
Вопрос скорее не про поломку совместимости, а про то, что через определённое время, вы встретите код нового стандарта в чужой зависимости

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

Как подобные проблемы решаются в других языках, в частности, в Ruby и Python?

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

Вы не анализировали изменения в быстродействии скриптов с paraquire и без?

Насколько я помню, просадка около 40%.

Хорошо было бы добавить возможность мокать встроенные модули ноды, то есть вместо реального fs отдать например Proxy от него, в котором пускаем модуль только к функциям чтения

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

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

Ну элементарно же! Нужно ограничивать права этого пакета другим пакетом для ограничения прав!

Гарантий я дать, конечно, не могу, но истинный параноик не будет подключать paraquire через node_modules, а аккуратно прибьёт его гвоздями к проекту. Или хотя бы зафиксирует версию.
Что же касательно самой защиты песочницы, то тут я тоже не абсолют, но код невелик и открыт для аудита. Простенькие трюки вроде (0,eval)(code) не проходят.

Да, разграничение доступа к fs — более чем актуально.

Про патчинг require в одном месте уже идёт обсуждение: github.com/nickkolok/paraquire/issues/1
Про разграничение доступа к FS — posix. setreuid, setregid, chroot, и «попробуй выбраться». Для своих сервисов эту комбинацию уже выучил наизусть. И даже бинарные модули ничем не спасают — хоть ты обдёргайся fopen и иже с ними — chroot он «в одну сторону».
Спасибо, посмотрел. Как я понимаю, эта штука — она для всего процесса? Т.е. порезать в правах только конкретную библиотеку нельзя, не шаманя с многопроцессностью?
В любом случае, выглядит годно. Можно чуть более подробный мануал по тому, как тремя командами загнать приложуху в chroot jail?
Для примера — nobody, nogroup, working directory.

posix.chroot(process.cwd()) // загоняем программу в chroot рабочей папки
posix.setregid(65534,65534) // задаём группу nogroup
posix.setreuid(65534,65534) // задаём юзера nobody

Сначала ставим папку (у непривелигированного юзера может не хватить прав, и команда зафейлится). Потом ставим ID группы (если сначала поставить UID — лишимся прав на смену GID). И под конец UID. Всё.

Единственная проблема — работает это только из-под рута. Но и просто (из userspace) сделать chroot в папку проекта — тоже хорошая практика. php-fpm только так и загонял, и не раз спасало от шеллов. То-есть, шелл-то в дырявый движок пролезал, но сделать с его помощью в chroot можно было примерно ничего.
В Java была технология Java 2 Security Permissions, но после того как всякие полезные библиотеки от Apache стали требовать AllPermissions, то от дальнейшего развития этой фичи отказались.

Что вы такое говорите? Кто отказался, где?

Как минимум, я в своих проектах давно отказался. А это уже немало.

Ну т.е. надо так понимать, что пруфа не будет?

Мысль-то хорошая, но вот попытка решать данную проблему (безопасность зависимостей) на уровне менеджера зависимостей не кажется удачной. Каждый должен заниматься своим делом, npm — управлять зависимостями. Ну например, Nexus (который в мире java можно считать аналогом npm) вполне себе умеет заниматься такими вещами, как отслеживать, в каких версиях конкретного пакета нашли уязвимости, и сообщать об этом пользователю.


А решать, что можно разрешить в конкретном приложении, а что нельзя — ну не его это дело. Да и нет у него для этого достаточно информации.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации