
Vulnerability #319816 – npm fails to restrict the actions of malicious packages - sebastianmck
https://www.kb.cert.org/vuls/id/319816
======
kibwen

      > npm encourages the use of semver, or semantic 
      > versioning. With semver, dependencies are not locked to 
      > a certain version by default. For any dependency of a 
      > package, the dependency author can push a new version of 
      > the package.
    

I don't see how this has anything to do with semver. Semver doesn't say
anything about not locking dependencies to a certain version (i.e., locking to
a specific version is totally legal), nor does it have anything to do with
allowing package authors to push new versions of their packages (I'm not even
sure how to parse this sentence, really... should it be impossible to ever
push new versions of a package? (EDIT: maybe it's suggesting there should be a
central review process, like the iOS App Store?)).

In fact, the semver spec doesn't even advocate automatically upgrading when
new patch versions are released:

 _" As a responsible developer you will, of course, want to verify that any
package upgrades function as advertised. The real world is a messy place;
there’s nothing we can do about that but be vigilant."_

[http://semver.org/#why-use-semantic-versioning](http://semver.org/#why-use-
semantic-versioning)

~~~
inglor
When you `npm install` a package by default when other users `npm install` it
it will install the most recent patch version - even if it's different from
the one you installed. So if you install dependencies through `npm install
--save` which is the default and advertized way - you can get completely
different code between production and staging.

As a library maintainer, patches breaking the library is something that
happens (not often, but still) - testing can eliminate a lot but not all bugs.

~~~
DrJokepu
I mean, don't use `npm install --save` then. I'm not really sure why people
started using it in the first place, it's such a lazy thing to do. Instead,
add it to your package.json yourself with the exact specific version you want
(none of the ^a.b.c funny business).

~~~
Silhouette
_Instead, add it to your package.json yourself with the exact specific version
you want_

Unfortunately, the same problem then arises for your dependencies. If any of
_them_ don't specify exact versions, you are still vulnerable to getting
uncontrolled changes.

This is why things like npm shrinkwrap exist, but it's still crazy that NPM's
default behaviour is the uncontrolled case.

~~~
DrJokepu
Yes, libraries should specify exact versions as well, it's insane that they
don't.

------
blaisio
Unless I'm not understanding this correctly, every package manager is
vulnerable to this attack (along with many others). I'm not sure why someone
bothered to write this down and make an official "disclosure". Maybe someone
more knowledgeable can explain?

I mean really the idea is just that if someone got somebody else's password,
they could use it to trick other people into installing a program. Even email
has this problem. So really the only thing NPM could be accused of here is not
doing more to make publishing secure (like using two-factor authentication).

~~~
BenjaminCoe
Similar problems exist in most package management systems. registries that
have a manual review process mitigate this danger, but there's still always a
risk of malicious code getting into the world.

Having said this, we'd like to make exploits such as those discussed in
#319816 as difficult as possible. We're exploring supporting new
authentication strategies: such as 2-factor authentication, SAML, and
asymmetric key based authentication (some of these features are already
available in our Enterprise product, but haven't made it to the public
registry yet). npm's official response has more details on this subject:

[http://blog.npmjs.org/post/141702881055/package-install-
scri...](http://blog.npmjs.org/post/141702881055/package-install-scripts-
vulnerability)

~~~
raesene3
Unfortunately I don't think that many/any of the Programming language package
repositories have manual review processes, or even automated checking for
things like known malware...

Linux package managers are a different story of course.

~~~
semi-extrinsic
Firefox has automated "malware checking" for extensions, the Mozilla AMO
Validator, and it's been basically torn to pieces by the community for being
not actually secure [1] plus a major hassle for developers [2], to the point
that large extensions with hundreds of thousands of users have stopped using
the official Firefox extensions repository.

[1]
[https://bugzilla.mozilla.org/show_bug.cgi?id=1227867#c2](https://bugzilla.mozilla.org/show_bug.cgi?id=1227867#c2)

[2] [https://forums.zotero.org/discussion/28847/zotero-addon-
is-n...](https://forums.zotero.org/discussion/28847/zotero-addon-is-no-longer-
available-at-mozillas-addons/)

~~~
raesene3
Yep it's a really nasty problem for any package manager that operates at
scale.

The problem is that without any centralized validation of packages, it leaves
checking to each developer who uses the libraries and obviously from an effort
standpoint that just makes it worse (i.e. if it's hard for the repo owner to
do validation it's hard x number_of_users for it to be done by end users)

~~~
semi-extrinsic
The problem is basically how the centralized validation is supposed to work.
For e.g. the Linux kernel, it's doable because all code in the kernel must
(almost by definition) interact with some other part of the kernel. Thus
someone else than the code owner, being responsible for those other parts of
the kernel, can be tasked with signing off on the new code being good and non-
malicious.

But for NPM or PyPI, where anyone can upload anything, how's that supposed to
work? It's perfectly fine for someone to put a package called "removeallfiles"
on PyPI which executes "sudo rm -rf /". This isn't (by itself) malicious code.
The same code, but obfuscated and put in the package name "isarray", is
perhaps obviously malicious. But what about something in the middle, e.g. some
form of practical joke package? What central authority decides what is allowed
and what is not on PyPI?

Signing is a tangential issue. As long as you're trusting the dev who uploaded
the code, what difference does it make whether they used password or public
key auth (effectively)?

~~~
raesene3
Well if there's no central validation, that leaves all individual users to
validate packages before use (which is a huge amount of work)...

The problem is that companies are using these packages as though they are
trusted (i.e. not validating them when using them), and that's part of the
value proposition in the first place (i.e. it's easier to use this package
than write it myself), but it's missing the cost of validation.

On signing I'm not sure we're talking about the same thing. I'm referring to
developers cryptographically signing packages before pushing to the
repository, with a key that the end-user can validate. the idea is to protect
against a comrpomise of the repository. There's a good discussion of the risks
and potential solutions on The update framework's site
([https://theupdateframework.github.io/](https://theupdateframework.github.io/))

~~~
semi-extrinsic
I completely agree on the first two paragraphs.

Wrt. signing: I'm assuming we are talking about PyPI and NPM here. Also I'm
assuming the major threat vector for a repository compromise is that (some of)
the dev's accounts on some other services (most likely email) are somehow
compromised. In which case it's down to that dev's OPSEC practices whether the
repository can be compromised using the data from $OTHER_SERVICE. If the dev
has poor OPSEC and would reuse the password for multiple accounts in a
user/pass repo auth scenario, it's reasonable that this person would also have
emailed themself the keys for signing packages, e.g. for transferring to
another location behind a firewall. In either case, you're down to trusting
the dev's OPSEC.

IMO, the threat models for other kinds of compromises which signing protects
against are much more far fetched. AFAICT neither PyPI nor NPM use third-party
mirrors, which basically leaves MitM attacks. If an attacker is capable of
successfully MitM-ing the connections you make to PyPI/NPM over https, you
have much bigger problems.

Or am I missing your point here?

~~~
raesene3
Ah yes, so the threat model for developer signing is compromise of the
repository. So here we're looking at the OpSec of the repository owner (e.g.
PyPI, npm, Rubygems etc), and also the risk of deliberate compromise by the
repo. owner (for example where a state with authority over the repository
compels them to modify a package)

In terms of compromise there's already been the attack on Rubygems in 2013,
but in general the thought here is that these repositories are extremely
tempting targets for well-funded attackers. A compromise of npm for example
would give an attacker direct access to a very wide range of targets.

Combine this with the very limited resources of the repository owners (most
are free resources, likely constraining the money available for defence) and
you get a realistic risk of attack, which is mitigated by an appropriate use
of signing by the developer.

Docker hub has deployed an implementation of the Update Framework to address
this, although the interesting point now is whether people actually use it as
it's not compulsory...

------
userbinator
I have a feeling that a lot of other systems also provide "the capability for
a self-replicating worm", as that's just the nature of computers in general,
and part of why they're so very useful.

To me, the fact that this "vulnerability" requires explicit user action, akin
to deliberately downloading and running malware, says that it's really a
property of all software ecosystems in which people can publish and
disseminate freely.

In that respect, it's nice to see a "this is as intended" response instead of
the typical direction of coming up with a set of more draconian policies and
processes merely to protect users from themselves.

But given what "security research" these days seems to involve, I can almost
imagine in the future: "Vulnerability #1048576 - computer allows users to
perform potentially malicious actions."

~~~
vjeux
As a developer in the node ecosystem, you run npm install multiple times a
day. If one of the dependency you require has been infected, it will look for
all the packages you own on npm and will publish a new infected version. Now
any time another developer that has one of your packages as dependencies does
npm install, it will infect that person again.

Once it reaches a package like left-pad that is used by a ton of libraries, it
will instantly infect hundreds of thousands of developers.

~~~
NathanKP
Solution:

1) Pin your packages to a specific version. If you aren't doing this already
they you are in for a world of hurt when someone who doesn't know what they
are doing releases a breaking package change on a minor version number.

2) Shrinkwrap your packages. Once again if you aren't already doing this then
you npm install will probably break about once per three months when someone
pushes a bad package to NPM.

3) Publish your NPM packages from an NPM in one vagrant development
environment and run your code that installs from NPM in another vagrant
development environment. If you have one shared environment then you are going
to have other issues of which the small chance of an NPM worm is probably
going to be the least of your worries.

~~~
jjnoakes
You forgot (4): either never upgrade (missing out on security and bug fixes)
or audit every update to every package which you are pulling down (which in
node could be thousands)

I prefer sticking to curated sets of packages with groups of people focused on
doing the auditing and security along side my due diligence. I get security
updates, bug fixes, far fewer breaking changes, regular updates, reasonable
assurance that code works together, and lower risk.

NPM has a lot to learn.

~~~
NathanKP
> curated sets of packages with groups of people focused on doing the auditing
> and security along side my due diligence

How does this differ from how NPM works? For example the set of packages that
is utilized by Express is downloaded more than 5 million times per month.
There are tons of eyes all over those packages.

Sure if you are installing sketchy packages that have 100 downloads a month
you have to do a lot of auditing yourself, but when sticking to the core
modules that are used in practically every node project you can benefit from
the auditing being done by all the others who use those packages.

~~~
jjnoakes
I don't think there are eyes all over those packages though.

------
pfooti
I feel like, with the left-pad fiasco, the node dev world (and the broader
programmer community) is rediscovering the web of trust that makes open source
feasible.

I mean, if I distributed a library through some other package manager system,
like a .jar file or some code that you install via homebrew, pip, or
./configure.sh && make, I can embed malicious code in the source somewhere.
Maybe not all automated package managers are quite as vulnerable to install
hooks, but all open source code is vulnerable to trust attacks, at runtime if
nowhere else. I ultimately trust the process that gives me nginx enough to let
it serve up my code, hoping there's not a backdoor somewhere that is shoving
environment variables (and therefore API keys) out the window to a hacker.

You can't assume people are going to review every line of source before they
link against a library. You can't assume people aren't going to click that
link that looks like a download link on a sourceforge page but is, in fact, a
crapware link. People make mistakes all the time.

So, yeah, there's probably room to make npm a little more robust and difficult
to specifically target as a vector. But thousands of developers are still
going to be writing sass, and using node-sass to build that, which needs to
download, compile and execute a binary on the devbox. Making the installation
process of libsass take an extra step or two is great and all (and annoying,
and probably likely to degrade windows node development most of all, since
windows libraries are harder to put in a "standard" place if you're a non-
windows dev writing a node library), but people are still going to be running
libsass binaries on their local machine without auditing it, trusting that the
developers there have good opsec and review everything well.

On the other hand, all this publicity means someone's bound to actually try
and build stuff that exploits trust here, either wormlike or just executing an
rm -rf in an install hook. So, my trust levels are lowered and my productivity
impaired because I'll be auditing more closely all the updates to existing
plugins I'm using. Win?

~~~
chromakode
I've been tinkering on one approach to trustworthy OSS ecosystem at
[https://github.com/chromakode/signet](https://github.com/chromakode/signet).

The OSS world has grown precipitously in the era of GitHub/npm/etc, and the
trust model hasn't caught up. It's not tenable to maintain a GPG keychain for
a nested tree of 100 dependencies. Neither is it advisable to keep deferring
this problem. We need to come up with a solution for tracking reputation and
trustworthy dependencies at this new scale. It's not simply a problem that
package repositories like npm can solve for us -- the scope of this problem is
human, and an ideal solution will work for both users and developers, and
apply to source distributions and multiple package repositories. One of the
few silver linings of the events of the last week is that more people are
aware of and pondering these issues. I hope we'll see some more discussion and
experimentation in this space!

~~~
Gracana
I like the general idea of this and I think it would be interesting to see how
various software hubs could integrate it.

An easy thing you could do right now is to put an attestation directory right
into your git repo. Then write up your comments (maybe in a file format
similar to what you're doing with signet) and do a signed commit into that
directory.

------
nickpsecurity
Here's a reference work with links to key papers on build system security for
anyone trying to improve them:

[http://www.dwheeler.com/essays/scm-
security.html](http://www.dwheeler.com/essays/scm-security.html)

Dig into archive.org for Shapiro's OpenCM while you're at it as it had a lot
of nice properties. Aegis seemed to as well. Pulling good traits from
Wheeler's survey into modern ones would be a good idea. Also, one can re-
develop OpenCM, Aegis, etc to have modern features like plugins for common
languages/apps or DVCS capabilities.

SCM security techniques date back to 80's-early 90's. No excuse for today's
solutions to still lack the basics.

------
raesene3
Kind of amusing that this is considered to need a new vuln. report, I kind of
assumed it was common knowledge.

Most of the programming language package repositories (e.g. npm, rubygems,
PyPi, NuGet) have this kind of installation process and limited/no checks for
malicious content.

Also as there's no consistent use of package signing by the developer (it's
either unsupported or not very used) there is also a risk of the repository
itself being compromised.

I did a talk last year for OWASP AppSecEU that covers this kind of thing.
[https://www.youtube.com/watch?v=Wn190b4EJWk](https://www.youtube.com/watch?v=Wn190b4EJWk)

~~~
semi-extrinsic
A very insightful look at package signing, and why it wouldn't actually
improve security for PyPI, by Python packaging guru Donald Stufft:

[https://caremad.io/2013/07/packaging-signing-not-holy-
grail/](https://caremad.io/2013/07/packaging-signing-not-holy-grail/)

~~~
jessaustin
What a great link: topical and well-reasoned! The concluding sentence is
interesting: " _My biggest hope is that we’ll get a solution where the end
user has the relationship with the source of trust and not the package
author._ " If one runs one's own npm registry and audits everything that goes
into it, one can have that already with npm.

~~~
semi-extrinsic
Yes, that closing remark is very interesting. It would essentially be
formalising what we somehow do manually/instinctively today: "Installing
numpy/react/etc.? Yes, everyone I know trusts that, so I do too." "Installing
random small non-popular package? I better have a bit of a look at the code
first."

------
ktRolster
And no intention from NPM to fix (according to the article)

~~~
mkagenius
What solution can we propose?

~~~
paulirish
NPM could take a few actions. The original disclosure PDF[1] suggests these:

● Automatically expire login tokens

● Require 2 factor auth for publish operations

● Help users be logged out during install operations

vjeux mentioned a few others on HN a few days back[2]:

● pre-install/post-install scripts should require user to accept or refuse.

● make shrinkwrap by default (and fix all the issues with it) so that running
npm install doesn't use different versions when used over time.

● make updating a version an explicit decision via npm upgrade

[1] [https://www.kb.cert.org/CERT_WEB/services/vul-
notes.nsf/6eac...](https://www.kb.cert.org/CERT_WEB/services/vul-
notes.nsf/6eacfaeab94596f5852569290066a50b/018dbb99def6980185257f820013f175/$FILE/npmwormdisclosure.pdf)
[2]
[https://news.ycombinator.com/item?id=11341145](https://news.ycombinator.com/item?id=11341145)

In the meantime, users may want to consider one of the following:

    
    
        npm config set ignore-scripts true
        npm logout

~~~
mayank
It's definitely a nuanced issue.

> ● Automatically expire login tokens

I don't see how this helps the issue at hand; a worm could spread very
quickly, requiring just a single publish from each freshly infected user.

> ● Require 2 factor auth for publish operations

This seems very reasonable, and the easiest to implement. It also has the nice
effect of being a captcha to the publish operation, which gives it some of the
gravitas it deserves in an open ecosystem like npm.

> ● Help users be logged out during install operations

This may break far more packages than might be considered acceptable.

> ● pre-install/post-install scripts should require user to accept or refuse.

Presumably this would be unnecessary with 2FA for each publish operation.

> ● make shrinkwrap by default (and fix all the issues with it) so that
> running npm install doesn't use different versions when used over time.

Doesn't do much to address the issue at hand. A static dependency tree doesn't
mean benevolent dependencies.

> ● make updating a version an explicit decision via npm upgrade

Same issues as shrinkwrap.

~~~
johannes1234321
> > ● pre-install/post-install scripts should require user to accept or
> refuse. > > Presumably this would be unnecessary with 2FA for each publish
> operation.

2FA still doesn't mean you can trust the install script. Not running scripts
automatically gives a chance to audit before they run.

And even with 2FA a worm could spread: It could manipulate the local npm
installation so whenever you want to upload a package it will modify it during
the publishing process giving you a 2nd-factor-request right when you expect
it.

The only way to prevent that I can come up quickly is to over a chance to
verify the package between signing (which npm doesn't support) and publishing.

~~~
pfooti
While I agree that giving people a chance to disable install scripts in order
to audit them before / after running is a good idea, I also think it's
somewhat optimistic of you to think that this would actually help.

There's plenty of npm packages that release updates weekly. I may not update
them every time (I tend to wait until I see a need), but the rate of update is
high enough that most people would just blindly click through after the
fiftieth time they installed that package.

------
notdonspaulding
"It rather involved being on the other side of this airtight hatchway"

[https://blogs.msdn.microsoft.com/oldnewthing/20060508-22/?p=...](https://blogs.msdn.microsoft.com/oldnewthing/20060508-22/?p=31283)

~~~
myhf
This is not the first time I've seen that argument used to justify ignoring
persistence attacks.

------
chromakode
In development, you should separate your npm publish credentials from your dev
execution environment. Use some kind of sandbox where you `npm install` -- a
VM is best.

In production, you should review the packages in your dependency tree and
ensure that the exact version you reviewed is what you deploy. To that end,
you should shrinkwrap your dependencies. Vendoring works well too. Shameless
plug: for additional strictness in your shrinkwrap, you can use
[https://github.com/chromakode/exactly](https://github.com/chromakode/exactly)
to store content hashes.

~~~
yoklov
Do people do this? It sounds unmanageable, especially if you publish packages
depending on other packages.

------
msoad
> 1\. Socially engineer a npm module owner...

Social engineering is not accepted in many security bounties. Just saying...

~~~
dkopi
It's a good thing the bad guys don't use social engineering either.

------
mchahn
I am always surprised when I hear of developers letting new versions of
dependencies go into production. I cannot imagine taking such a chance.

Even if every new version of the total app is tested heavily before
production, you lose the inherent stability of shipping the same code that is
known stable from the users over time.

Others have said it is important to use new versions of dependencies to get
the bug fixes but I don't see that as a good trade-off.

------
spankalee
Automatically running pre and post scripts is absolutely insane.

~~~
wycats
All package managers (that I know of) for dynamic languages offer a mechanism
for compiling native code for packages that include bindings to C libraries.

That mechanism could easily be used to achieve the same goal, even if there
was no explicit "post-script" mechanism.

~~~
derefr
Debian solved this particular problem a long time ago, with pbuilder(1):
packages that are installed "from source" simply get compiled in a chroot.
Strangely, nobody has ever copied the idea.

The modern hipster-language equivalent would probably be to make the package
manager depend on the presence of Docker/rkt/systemd, and use it to pull down
a dev-env container and build the native bindings in that.

~~~
ambrop7
Nix/NixOS - everything is build not only in a chroot, but also in various
namespaces. Of course that doesn't help if you actually use a package
(directly or indirectly) hence executing it outside of the build chroot.

~~~
digi_owl
Similar with Gobolinux, iirc. With a union mount on top to redirect the files
written during the install step.

Afterwards the sub-directory in /Programs can basically be turned into an
archive for future installs.

------
foota
It does seem to me like it would be reasonable enough to not have npm stay
logged in after running a command.

------
en4bz
Does npm require signing of packages?

~~~
raesene3
nope and not only that it's not even supported AFAIK

------
fibo
Just to share, there is an issue about uglifyjs
[https://github.com/mishoo/UglifyJS2/issues/936](https://github.com/mishoo/UglifyJS2/issues/936)

~~~
nailer
The uglify authors should use 'uglify' per the naming conventions and can
easily reserve uglify-js and uglifyjs as empty / legacy packages.

~~~
u223344
According to the parent link they've been waiting for npm support to respond
for a over a month.

~~~
nailer
They filed an official dispute 11 hours ago.

~~~
dps7t
Feb 4th.
[https://github.com/mishoo/UglifyJS2/issues/936#issuecomment-...](https://github.com/mishoo/UglifyJS2/issues/936#issuecomment-179937812)

------
diegorbaquero
Now that things like GreenKeeper exists, the ^ should be removed from being a
default thing.

~~~
jessaustin
Yes, but that should be done in a _patch_ update, so all the semver extremists
can ignore the semver violation like they did the first time when "~" was
switched to "^":
[https://github.com/npm/npm/releases/tag/v1.4.3](https://github.com/npm/npm/releases/tag/v1.4.3)
(note the "3" at the end, instead of "0")

------
inglor
It's disappointing to see you post this Sebastian, sure - Babel was affected
by the whole left-pad ordeal but is more drama what would really help right
now?

You're a doer - if you want to see something done about it at Facebook no one
is stopping you from forking NPM or contributing code to it.

