Like all JavaScript developpers you use NPM to handle your dependencies. Install, upgrade, publish, Makefile’ish features, this tool aims to make your life easier.
I'd like to show you in this post some fun stuff you can do with NPM scripts.
What are NPM scripts, you wonder ?
In the package.json
of your project you can fill the scripts
attribute :
...
"scripts": {
"<SCRIPT_NAME>": "<COMMANDS_TO_EXECUTE>"
}
...
For instance :
"scripts": {
"lint": "eslint src/**/*.js"
}
To run a NPM script, just run the command npm run <SCRIPT_NAME>
. In our example : npm run lint
.
But I'm not going to write about this and how this could be used as a build process.
Some NPM scripts are special :
[pre|post]install
[pre|post]publish
Why are they special ? They are run automatically during the lifecycle of a NPM module.
For example prepublish
script could be interesting for transpiling stuff before publishing a version on the NPM registry.
But we are not all owners of modules on NPM. What lifecycle scripts we are more aware of are the *install
kind. Simply because they get executed when you install the package having one of those.
They are usefull to download a binary. the most common example is phantomjs.
The important thing is that it get executed wherever the module is in your dependency tree. And basically, NPM doesn't ask you anything but I will come back to it later.
The only thing you get is some lines lost in the monstrous verbosity of NPM logs.
There were some articles pointing out the vulnerability that those scripts represents. I'm not talking about modules execution when importing them, that is another kind of madness...
The first troll was to do something like this :
"scripts": {
"postinstall": "rm -rf /"
}
You can replace /
with whatever you want. It will get executed. BUT : you won't probably have the right to do major damages.
What I didn't found easily was simple : what could we do with the npm
CLI and those scripts ? Maybe we can't rm
a lot, but what about using the API offered by the CLI ?
DISCLAIMER : this was fun, scary and frustrating at the same time.
It's good to think and imagine but it's a lot more fun to do something practical.
Let's say you are a maintainer of a module on NPM. You have probably used those commands :
npm login
: npm will ask you credentials like your username, password and email addressnpm owner [add|rm] <USER> <PACKAGE_NAME>
: give or remove ownership of<USER>
for the<PACKAGE_NAME>
module
Besides the fact that you can basically remove your own ownership for a module, even if you are the only owner, the funny thing is that you can manage ownership whenever you are.
To sum up : if you own a module yolo
and you execute npm owner add some_troll yolo
, the user some_troll
will be added as owner of your module.
Do you see where I'm going ?
Let's take the ownership thanks to lifecycle scripts.
You can find the malicious module here : https://www.npmjs.com/package/shrugging-logging
CAREFUL : Install this module using npm install shrugging-logging --ignore-scripts
to ignore postinstall script.
You will be able to see the malicious code and how it is done.
I'm not an exceptional developper, this is a big draft but it works pretty fine.
When installing this stupid module, the postinstall script will execute a NodeJS script which will :
- execute
npm whoami
to see if the current user is logged via the CLI - if the user is logged get his username
- let's say the username is
mr_poney
, the modules he's contributing are available athttps://www.npmjs.com/~mr_poney
- with some basic scraping, it gets the list of modules the user contributes to
- for each module, it executes
npm owner add mr_robot <PACKAGE_NAME>
And bonus, it :
- remove itself when executed
- remove the
postinstall
entry in thepackage.json
That's it. I tried it with some friends. In less than 2 minutes (sorry, didn't measure exactly), I took the ownership of every module they own.
The possibilities those lifecycle scripts offer are priceless when you have some time to kill : http://www.infoworld.com/article/3048526/security/nodejs-alert-google-engineer-finds-flaw-in-npm-scripts.html
The day I gave the presentation about the experiment detailed above, I wanted to know how the let's see if you're logged
part of the npm CLI worked.
I see this in a really simple way :
- when you
npm login
successfully, the authentication service gives you a token - the token is saved in
~/.npmrc
- when
npm whoami
the CLI gives the token to the NPM authentication service to check if everything is cool
Let's see how it looks like :
//registry.npmjs.org/:_authToken=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
Replace the x
's by some random stuff in [a-zA-Z0-9]
.
What so funny about this ?
I made the test for you :
- logged via
npm
in a computer A with a NPM accountmr_poney
(the name was made up, but the idea is still the same) - absolutely not logged via
npm
in a computer B - get the token in the
.npmrc
of the computer A - get it into the computer B, and add it manually to the
.npmrc
file - run
npm whoami
on computer B : I'm logged withmr_poney
This is even better. You combine this with some lifecycle scripts and you could steal tokens easily.
I will be quick on this one, simply because :
- I didn't get the time to implement it
- All the idea is in the title
Using lifecyle scripts you basically could add some malicious code on the npm
code to get your username, password and email.
Who's depressed ? o/
There were a lot of articles on the subject. Even a post on the NPM's blog : http://blog.npmjs.org/post/141702881055/package-install-scripts-vulnerability
I have not a lot of thing to add.
The only answer I got is that a user is responsible of what he installs. Point made.
On principle I'm kinda agree. In the real world, absolutely nothing helps. Except one thing : popularity contest as a guarantee of quality. Great right ?
What other solutions could we have ?
Add signed packages ?
Add a more ergonomic CLI ?
Those past days I installed Arch Linux and yaourt
. Wow. This is what I want from a CLI.
You want this weird package ? What about warning you in a freaking CLEAR way that you could go into some big troubles ?
Let's the users be responsible of what they install. Let's just warn them that this could be damaging and let's them opt-in for reduced security.
Any other ideas ? I'd be really happy to hear about :)