First of all, an affiliation change notice (this is recent):
I am currently working for Exodus and am sponsored by them to perform security research.
This was reported by me through the Node.js third-party modules program and was fixed in the just-released yarn v1.17.3. See also yarn blog post on this.
For scoped packages that are listed as resolved "http://registry.npmjs.org/@
... in yarn.lock
, affected yarn versions
transfer npm credentials (i.e. _authToken
) over unencrypted http connection.
This allows any MitM (for example, a proxy or a VPN, or a cafeteria Wi-Fi) to sniff out npm credentials,
given that the developer performs yarn install
on such a yarn.lock
file.
- 2019-07-12 - Found
- 2019-07-12 11:31 UTC - Reported to Node.js third-party modules program
- 2019-07-12 12:12 UTC - Triaged
- 2019-07-12 12:19 UTC - Yarn dev joined the conversation
- 2019-07-12 12:47 UTC - Initial response and confirmation from Yarn dev
- 2019-07-12 13:22 UTC - Fix PR created by Yarn devs
- 2019-07-12 14:19 UTC - Fix landed
- 2019-07-12 15:28 UTC - Fixed version (v1.17.3) released as
rc
- 2019-07-12 21:42 UTC - Fixed version (v1.17.3) released as
latest
- 2019-07-13 08:40 UTC - Yarn blog post published
The timeline was really good on this, this was found, accepted, fixed, and deployed in the same day.
Attacker (MitM) being able to:
- Impersonate the affected account
- Publish packages (including those containing malicious code) from the affected account that could also get used by the affected account/company in the future (for protected packages) and by anyone in the ecosystem (for public packages)
- Perform logout and break installs of protected packages
- Unpublish packages from the registry (with limitations)
A quick search shows that there is a large number of yarn.lock
files affected by this on GitHub, some examples.
There seem to be many of those.
Looks like not only it was possible to craft a yarn.lock
with a malicious intent, but also this seems to be a common pattern that yarn created itself at some point or under some circumstances and that gets persistent from older versions.
Yarn explanation (from their blog post):
For a few months in 2018, the npm registry returned http urls instead of the regular https ones. Although the problem seems to have been corrected earlier this year, the lockfile entries generated during this period may still reference http urls and cause Yarn to send authentication data unencrypted.
This looks very common, mostly coming from @babel
and @types
scopes.
This could also be exploited with malicious intent -- i.e. an attacker could construct/modify a yarn.lock
manually to exploit this vuln, not only as a result of that incident in 2018.
I do not think that these deps are something that is likely to be catched by PR review unless one knows what to look for (examples above show that it was not noticed), so anything in line with "users should have reviewed yarn.lock
files for this not to happen" does not work as a solution here. The problem is that users probably won't catch that packages being resolved to http://
could mean that the yarn lock is "compromised". A lot of such existing yarn.lock
files that no one noticed (even with scoped packages) indicate that.
Some historic examples in facebook repos: docusaurus (same file here), jest. Moreover, yarnpkg/yarn lockfile itself has a http://
-resolved dep: https://github.com/yarnpkg/yarn/blob/v1.17.3/yarn.lock#L5646. It's not scoped, but that difference might also be subtle while reviewing a PR. There are more of those in git history.
- Update yarn to v1.17.3 or later in all your environments.
- Go to Settings -> Tokens on npmjs.com and delete all listed tokens. Relogin where you need.
- Make sure that your
yarn.lock
files do not have entries resolved tohttp://
registries. - Enable 2FA on npm for logins and publishes, if you haven't done so already.
As a side note: periodically rotating your npm tokens (i.e. revoking them and doing a re-login) is a good idea in general. Also, don't forget to keep track of your tokens through the Settings -> Tokens page.
Use the following command, for example:
git log --pretty=format:"%h" -- yarn.lock | xargs -n1 -I{} git show {} -- yarn.lock | grep 'http:.*/@'
Example output that indicates that http://registry.npmjs.org/@types/node
has been used in jest
at some point:
jest chalker$ git log --pretty=format:"%h" -- yarn.lock | xargs -n1 -I{} git show {} -- yarn.lock | grep 'http:.*/@'
- resolved "http://registry.npmjs.org/@types/node/-/node-9.4.7.tgz#57d81cd98719df2c9de118f2d5f3b1120dcd7275"
resolved "http://registry.npmjs.org/@types/node/-/node-9.4.7.tgz#57d81cd98719df2c9de118f2d5f3b1120dcd7275"
resolved "http://registry.npmjs.org/@types/node/-/node-9.4.7.tgz#57d81cd98719df2c9de118f2d5f3b1120dcd7275"
+ resolved "http://registry.npmjs.org/@types/node/-/node-9.4.7.tgz#57d81cd98719df2c9de118f2d5f3b1120dcd7275"
It is not wise to do that with your active token, and you probably won't need this section, but here it is for anyone who is interested:
-
Perform an npm login or just write
//registry.npmjs.org/:_authToken=38bb8d1f-a39b-47d1-a78e-3bf0626ff77e
(which is the format npm uses) to ~/.npmrc. Doing this from your own account would leak your npm credentials on next steps, so better just use a placeholder. -
Create an empty package with a single dependency on
"@babel/core": "^7.5.4"
-
Perform
yarn install
-
Replace all occurrences of
https://registry.yarnpkg.com
withhttp://registry.npmjs.org/
in the generatedyarn.lock
Alternatively to steps 2-4 -- just use an already existing yarn.lock with
resolved "http://registry.npmjs.org/@
in it (lots of those on GitHub), but be careful with that. -
Clear yarn cache and node_modules:
rm -rf ~/.cache/yarn/ node_modules
. Let's assume you just downloaded an affectedyarn.lock
on your clean machine. -
Start wireshark with
tcp dst port 80
filter. -
Run
yarn install
Observed result is attached on a screenshot below:
Apart from the applied fix in yarn v1.17.3, I would ideally like to see the following changes:
- Disallowing
http://
for all repositories by default -- looks like planned for yarn v2. - Yarn fixing
yarn.lock
files automatically to replacehttp://
links to npm registry withhttps://
links. - Automatic revocation of all tokens that were transfered to npm registry via unencrypted connection on npm registry side.
Don't forget to enable 2FA on npm (and everywhere else). Do it now. For both logins and publishes.
Published: 2019-07-13 10:30 UTC.
If you have any questions to me, contact me over Gitter (@ChALkeR).