Paraquire, или Перестаньте доверять библиотекам

в 17:40, , рубрики: javascript, node.js, npm, open source, require, безопасность, децентрализация, здесь нет схемы шапочки из фольги, информационная безопасность, паранойя, параноя, права доступа, Программирование, разграничение прав доступа

TL; DR

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

Paraquire, или Перестаньте доверять библиотекам - 1

Под катом описывается proof-of-concept библиотеки, реализующей механизм загрузки npm-модулей с возможностью установить права подобно тому, как на Android можно выдавать приложению конкретные разрешения.

Вместо

var lib = require('untrusted-lib');

предлагается писать где-нибудь

var paraquire = require('paraquire')(module);

и затем

var lib = paraquire('untrusted-lib');

или же

var lib = paraquire('untrusted-lib', {builtin:{https:true}});

Исходный код доступен на гитхабе под LGPLv3.

Кроме того я, не будучи достаточно опытным NodeJS-разработчиком, прошу у сообщества советов и обсуждения.

Краткий анализ истории

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

  1. Отзыв пакета. В разрушительном виде продемонстрирован Азером Кочулу, отозвавшим пакет leftpad. В настоящее время неосуществим, т.к. отзыв пакетов очень сильно ограничен.
  2. Вредоносные preinstall- и postinstall-скрипты. В настоящее время — основной способ атаки (см. ссылку выше). Теоретически может быть легко пресечён флагом npm install, который бы приводил к выдаче предупреждения перед запуском любого shell-скрипта, чтобы уже сам программист мог решить, запускать ли предложенное. Одно дело, когда preinstall-скрипт пытается запустить PhantomJS (headless-браузер), и совсем другое — когда этого требует маленькая библиотечка для добавления пробелов слева к строке. Почему такой флаг до сих пор не введён — неясно.
    UPD: Спасибо SDSWanderer за комментарий о возможности сделать

    npm install --ignore-scripts
    

    Впрочем, интерактивный вариант (желательно с показом содержимого скрипта) был бы тоже не лишним.

  3. И, наконец, можно прятать вредоносную нагрузку непосредственно в исполняемый код пакета, как в шуточной форме описано здесь. Команда npm shrinkwrap, жёстко фиксирующая версию пакета, не поможет от «мин замедленного действия», которые, например, ждут определённой даты (вспомним «Чернобыль»). Кроме того, при нынешнем обилии зависимостей провести аудит исходного кода даже одной версии нужного пакета вместе со всем, что этот пакет использует — задача, конечно, посильная, но явно не повседневная.

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

Использование

В настоящее время API весьма небогато.
Итак, для начала загрузим саму библиотеку paraquire где-нибудь в относительно доверенном коде, т.е. коде нашего приложения:

var paraquire = require('paraquire')(module);

Обратите внимание на "(module)". Так как paraquire занимается управлением модулями, ему требуется знать, к какому именно модулю придётся подключать зависимости, поэтому подключение paraquire выглядит несколько необычно. Вы можете подключить paraquire к любому количеству модулей из вашего проекта.

Подключим теперь зависимости. Например, пусть untrusted-lib — библиотека, которой, по идее, ничего требоваться не должно (например, leftpad или imurmurhash). Тогда мы можем вовсе не давать ей доступ ни к чему, подключив её так:

var lib = paraquire('untrusted-lib');

Всё, попытка сделать require('fs') в коде untrusted-lib вызовет ошибку.

У функции paraquire есть и второй, опциональный, параметр-объект. Например, вот так можно предоставить библиотеке untrusted-lib доступ к встроенным модулям http и https, а также к консоли console и process.argv:

var lib = paraquire('untrusted-lib', {
    builtin: {
        http: true,
        https: true,
    },
    sandbox: {
        console: console,
        process: {argv:process.argv}
    },
});

Кстати, давать доступ ко всему process настоятельно не рекомендуется, в частности, из-за process.env и process.bindings('fs').

Прошу совета

Как уже было сказано выше, я не являюсь достаточно опытным разработчиком, и потому эта краткая публикация имеет своей целью скорее инициировать обсуждение. Актуальна ли эта тема? Как подобные проблемы решаются в других языках, в частности, в Ruby и Python? Может быть, кто-то может предложить способ обойти paraquire и получить повышенные привилегии?

В каком направлении следует развивать API? Может быть, стоит проконсультироваться у кого-то из зарубежных мэтров? Какие вопросы осветить в дальнейших статьях и стОит ли их вообще писать?

Автор: NickKolok

Источник

Поделиться

* - обязательные к заполнению поля