[HN Gopher] Embedded malware in RC (NPM package) ___________________________________________________________________ Embedded malware in RC (NPM package) Author : hjek Score : 95 points Date : 2021-11-05 17:13 UTC (5 hours ago) (HTM) web link (github.com) (TXT) w3m dump (github.com) | tolmasky wrote: | If you're interested in preventing this sort of thing, I'd | appreciate comments on this | [RFC](https://github.com/npm/rfcs/pull/488) I just submitted to | npm to make install scripts _opt-in_ instead of _default | behavior_. While of course not perfect, this simple change would | certainly go a long way in increasing the difficulty in creating | these sorts of attacks, as right now as long as a computer even | installs the packages in question, not even running any code in | the package, the malicious program has a chance at running. | | RFC: https://github.com/npm/rfcs/pull/488 | | Related HN post: https://news.ycombinator.com/item?id=29122473 | ricardobayes wrote: | Non-tech solution is to pin versions and read (hacker)news | before updating. | forty wrote: | Isn't that only useful for the very rare minority of people | that install packages then don't run the code right after? :) | schemescape wrote: | I initially had the same thought, but I came to a different | conclusion because the presence of an install script is not a | given for most packages. | | For example, if I installed a command line arguments parser | and it claimed to require running a setup script, I would | immediately be very suspicious. | | For a huge package like TypeScript, I'd probably just | immediately let the script run and trust Microsoft to not | publish malware (and NPM to not change the package contents). | tolmasky wrote: | The fact that they don't need to be run is what makes them so | hard to spot and easy to sneak in. This allows for some very | hard to mitigate strategies. It's easier to sneak in packages | into metadata files like package.jsons than create "excuses" | to import files that aren't really needed. It can be abused | in more complicated ways as well, like for example: | | 1. You add a totally "safe" dependency that you control, | let's call it "shell-dependency", as an innocuous part of a | PR to a "popular-package". Again, even if you inspect this | package, it's totally fine. The current version of shell- | dependency is 1.0.0, but it of course goes into the | package.json as "1.x.x" | | 2. You now add malicious dependency to shell-dependency, and | bump shell-dependency to 1.0.1, meaning every consumer of | "popular-package" now gets your "malicious-package". | | Notice that this was accomplished with zero traceable GitHub | history. Unless every package up the line uses a package- | lock.json (which is explicitly recommended against unless you | are an end-user application), "malicious-package" is able to | enter the dependency chain undetected. _If_ it required some | sort of code import, then it would have more opportunities to | be spotted. There are of course ways to do this with attacks | that require running code as well, but this makes it super | easy, especially considering that people often install | packages as root, even when they run their apps not as root. | Me1000 wrote: | Many packages on npm are not run in a local node environment, | they run in a browser which is sandboxed. | | Regardless, just because there are additional vectors to | exploit the user doesn't mean closing off one vector isn't | worth doing. | chakkepolja wrote: | Problem is, as someone mentioned in the GitHub thread as well, | people just press OK, especially if it's a transitive | dependency. | tolmasky wrote: | I've answered this on GitHub, but will provide an answer here | too. Packages aren't like end-user software, for two | important reasons: | | 1. The people involved are developers using development | components, who are much better equipped to understand an | installation failure and take proper action than an end user | who is just trying to click through to play a video game or | something. But more importantly: | | 2. The vast majority of the installs happen on automated | machines (like CI), where you definitely want to fail when | something drastically different happens like a new package is | all of a sudden running a script. The tests would fail, you | would look at the reason, it would be because some weird | script is about to run on the machine, and you'd adjust your | PR accordingly. This allows multiple levels of consideration: | 1) the original author deciding to do something about the | failed install (even if it's just appending the flag and not | thinking about it), and 2) the PR reviewer having code-as- | data evidence that this code change would mean new foreign | code _not represented in the commits_ will be running on | their machines. This is huge. | | The other important point about this is that packages | precisely have different risk models depending on whether you | are installing things locally or on production machines, | where a malicious package could be catastrophic. That's why | the RFC allows you to have individual user configuration | where you explicitly allow certain packages "from now on" | (which is I think the way most people want to think about | this: the first time you install something and it warns you | and you look into it, but from then on you say "this one is | good"). On the other hand, the actual scripts and repository | have no such configuration and thus require the installation | to include the specific flags, again, clearly documenting the | expected results of a seemingly innocent process that can | actually currently have bad consequences. | Me1000 wrote: | I don't think anyone is claiming making install script opt-in | will fix all the problems. But adding any friction | discourages the behavior. And if the norm changes, it's | reasonable to believe package maintainers will start opting | for dependencies that don't require install scripts. Thus | further encouraging people to not include them with their | packages. | Me1000 wrote: | Your comments on the RFC cover this, but I want to highlight a | two things: | | 1) The vast majority of packages on npm don't require install | scripts to work. | | 2) Many of them that currently include install scripts are just | ads asking people to contribute code or money to their project. | BonoboIO wrote: | Using npm is like russian roulette. Someday it makes your head | hurt really bad! | rndhouse wrote: | I've created Vouch in an attempt to address this problem: | | https://github.com/vouch-dev/vouch | | Vouch lets users create and share reviews for NPM packages. | Project dependencies can then be checked against those reviews. | | Vouch uses extensions to interface with package ecosystems. It's | simple to create a new extension. Extensions currently exist for | NPM, PyPi, and Ansible Galaxy. | | I'm currently working on a website to index known reviews and | publish official reviews. | | I hope you guys find it useful! Drop by the Matrix channel if you | have any feedback to share: #vouch:matrix.org | schemescape wrote: | > The check command generates an evaluation report of local | project dependencies based on available reviews: | | Is there an example of a generated report? | hjek wrote: | Sounds really interesting. I'm kinda scared to use npm right | now to be honest. What dangers may be hidden deep in the | dependency trees? But is a _review_ of every _update_ then | done, because rc used to be a legit package? | rndhouse wrote: | A review corresponds to a particular version number. But the | review process does not need to start from scratch with each | version number increment. Reviews from previous versions can | be leveraged to lessen the workload. | yjftsjthsd-h wrote: | I'd like this to work, but it seems like it relies on packages | being statically "good" or "bad" - what happens if a package is | legitimately well trusted but then the main dev gets backdoored | and a bit of extra code is injected? | rndhouse wrote: | That extra code will stand out as not having been reviewed. | yjftsjthsd-h wrote: | Oh, you mean to have reviewers look at every line of every | version (at least cumulatively). Yes, that would work, and | while the effort is significant I appreciate that it's the | effort you'd want regardless so this helps share the load. | rndhouse wrote: | That's right! The purpose of Vouch is to continually | decrease the cost of the review process with each new | review. | hn_throwaway_99 wrote: | That's not really the solution for this problem, though, which | is very specifically when a project maintainer's account gets | compromised, so then the bad guys publish a new malicious | version of that library that gets picked up by anyone using | non-pinned NPM versions (i.e. most everyone). | | There are a couple more straightforward ways to do this: | | 1. Require 2FA, ideally hardware key 2FA, for anyone publishing | a package with any sizable following. | | 2. Make running of preinstall/install scripts opt-in. | | 3. Make the semantic versioning syntax optionally more | restrictive. If I specify I want version ^2.2.1, I'd like to be | able to specify that I DON'T want to pull 2.2.2 the moment it | becomes available, but perhaps want some amount of latency | before pulling that. | ricardobayes wrote: | 2. Make running of preinstall/install scripts opt-in. They | already are, npm install --ignore-scripts | hn_throwaway_99 wrote: | What you've written there is literally the definition of | opt _out_. | rndhouse wrote: | Reviews in Vouch refer to a particular version of a software | package. If a new release is issued by a malicious actor, the | new release would require a new review. | | But the review process does not need to re-start from | scratch. Reviews from other versions can be used to lessen | the workload. | | On the subject of automatically updating packages: the Vouch | dependency analysis can be included in CI. Un-reviewed or | review failing dependency updates can be flagged for | attention. | throwaway984393 wrote: | Who's reviewing the software package's dependencies? | binarynate wrote: | This is why JS runtimes should add the ability to set permissions | on a per-module basis. Deno is a step in the right direction by | requiring permissions for a script to be specified (e.g. deno run | --allow-read --allow-net myscript.ts), but the permissions are | global for the entire script and can't (yet?) be configured | differently for each module / dependency. | bluefox wrote: | Is the advisory genuine? | | It links to the github repo, where the latest commit is from 2018 | for version 1.2.8. | | It links to npmjs page, that shows 48 versions, where the latest | version is 1.2.8 from "3 years ago". | | Yet it has 1.2.9/1.3.9/2.3.9 for "Affected versions". | | Did npmjs "revert" these versions and any clue of their | existence? The npmjs page links to dominictarr's repository. The | npmjs site doesn't seem to have a "who owns this package name" | besides the repository/homepage links. Very confusing. | | I remember some years ago there was some story involving the | original author's handing maintainership rights to some shady | dude. Is it about that time, or is it about something more | current? | speeder wrote: | I saw in one of the repos the maintainer confused about what | happened, seemly someone somehow impersonated him and released | new versions to npm without actually touching the repo itself! | hn_throwaway_99 wrote: | One of the thing I wish was really much easier to do with NPM is, | when running `npm update`, to only pick up the most recent | compatible versions _from X days ago_. | | That is, for sensitive apps, I _don 't_ want to use versions that | are less than, say, a month or so old unless I specifically | override it. I want to stay up-to-date but not _too_ bleeding | edge, specifically to avoid situations like this. | ricardobayes wrote: | Seems like a good choice to work at a cybersecurity company these | days. Job security is guaranteed. | hjek wrote: | More info: https://therecord.media/malware-found-in-coa-and-rc- | two-npm-... | bluefox wrote: | Thanks. | | > Since then, the npm security team has removed all the | compromised coa and rc versions to prevent developers from | accidentally infecting themselves. | | Removing all trace of evidence is not something "security | teams" should do. Instead of sweeping security incidents under | the rug (where twitterverse resides), they should at least | mention the existence of these versions and that they contain | malware on the package page. | schleck8 wrote: | And yet again, twice in a row this time. | | Note how the referenced Virustotal result has 40+ detections [1]. | I'm still wondering why info like this isn't used by Pypi and | NPM. Chocolatey has Virustotal integration for all releases. | | And it's not like Virustotal is the only option, there is Cape | [2] for dynamic execution, Metadefender, and Intezer Analyze just | to name a few. | | Really confusing for such a vital supply chain component to be | this easily abused. | | One of the highlights is when someone recently used NPM to spread | ransomware via a fake Roblox API package.[3] | | [1] | https://www.virustotal.com/gui/file/26451f7f6fe297adf6738295... | | [2] https://github.com/kevoreilly/CAPEv2 | | [3] | https://www.reddit.com/r/programming/comments/qgz0em/fake_np... | megumax wrote: | I think that volunteers (some of them maybe paid) should check | the validity of code, at least for projects over 10-100k | downloads. In case of crates.io (Rust), there is cargo-crev[1]. | Also, npm should popularize 2FA. | | [1]https://web.crev.dev/rust-reviews/ | m4rtink wrote: | Say, like package maintainers do for major Linux | distributions ? | iancarroll wrote: | It's not clear that this would be useful; at least for the coa | package, the DLL was downloaded dynamically via a script, so | NPM would not have been able to detect it unless the script | itself was flagged. Not sure what Chocolatey does, but it's | also hard to threshold on VirusTotal when there are a lot of | FPs by random vendors. | schleck8 wrote: | I think Chocolatey has manual screens when there are more | than 5 detections, but not entirely sure | schemescape wrote: | Given that these attacks are becoming increasingly common, | package registries could at least install each package (prior | to publishing) in some isolated container or VM and then run | some similar malware detection on the resulting file system. | | Honestly, I'm strongly considering moving away from the NPM | ecosystem because it's clearly become a target for malware. | thrashh wrote: | But attackers are not dumb. They would circumvent whether | loose checks the package manager may have. Just considering | your suggestion, the obvious immediate exploit is to not | deploy the attack payload right away. Nothing you will | think of will evade defeat. | schemescape wrote: | I agree that it is an unending arms race, but if NPM | doesn't even plug obvious holes (like running install | scripts by default), then they've lost my trust. | | Edit: if anyone knows of a way to disable NPM from | running install scripts automatically (without having to | remember to specify --ignore-scripts on each invocation), | while still allowing me to use "npm run" to manually run | scripts (e.g. test scripts for my own packages), I'd love | to hear about it. | Ginden wrote: | npm ci --ignore-scripts | schemescape wrote: | Ah, sorry, I meant a way to _automatically_ do that (both | so I don 't have to type as much and so that I can't | accidentally forget to add that argument). Edited my | comment to reflect this. | jffry wrote: | You can run this npm config set ignore- | scripts true | | which will update ~/.npmrc (you can also create project- | specific .npmrc files if you prefer) | | You can see how NPM has been configured by running | npm config list | yardstick wrote: | "Don't let perfect be the enemy of good" | woodruffw wrote: | > Note how the referenced Virustotal result has 40+ detections. | I'm still wondering why info like this isn't used by Pypi and | NPM. | | I was contracted to help build a malware analysis pipeline for | PyPI[1][2]. We don't currently have a VirusTotal | detector/analyzer (IIRC, we couldn't get a high-enough volume | API token on short order), but I think any work towards that | would be _greatly_ appreciated by both the PyPA members and the | Python packaging community! | | [1]: https://pyfound.blogspot.com/2018/12/upcoming-pypi- | improveme... | | [2]: | https://github.com/pypa/warehouse/tree/main/warehouse/malwar... | qwerty2021 wrote: | I checked the readme of both those packages and I can't for the | life of me understand why would anyone use either of them. | | Why the fuck do all these leftpad is-even hello-world tic-tac-toe | packages have millions of downloads? | afavour wrote: | Command line argument parsing and config loading both seem like | very sensible library abstractions to me. This isn't leftpad. | havkd wrote: | Command line argument parsing and config loading both seem | like something that the standard library should provide. | megumax wrote: | Ok, now, what languages beside Python and Go provide | Command line argument parsing? And Go doesn't do that in a | `professional` way. You either write your own, which can | easily turn into a clusterfuck or use a third party | library. Even in Go, people use cobra[1]. Also embedding a | lot of functionality in a standard library isn't great as | well, because if some vulnerability is found, it's really | hard to patch it, because you need to push versions and | (for example on Linux) some distro maintainers won't push | it for `stability` etc. A standard library should provide | basic functionality (in most general areas), but not very | advanced one. | | [1] https://github.com/spf13/cobra | kitsunesoba wrote: | It's not part of the standard library, but Swift has the | first-party ArgumentParser[0]. Other languages could use | a similar model (though what "first party" means for | JavaScript is unclear). | | [0]: https://github.com/apple/swift-argument-parser | morelisp wrote: | > what languages beside Python and Go provide Command | line argument parsing? | | Even POSIX gives you getopt(1) and getopt(3). What other | language doesn't? I can only think of Java. | afavour wrote: | Sure, and Node gives you the process.argv array. The | point is having higher level APIs than that. | voldacar wrote: | it feels like the more higher level APIs we add, the | shittier and more annoying software becomes | morelisp wrote: | getopt is a higher-level API. process.argv is equivalent | to, uh, argv. | megumax wrote: | POSIX is not ISO C or C++ standard. On Windows, what are | you gonna do? | | Also, other languages are Rust, Kotlin, Swift (to name a | few `modern` ones). Yes, Kotlin and Swift have `first | class` CLI parsing libraries, but they are not part of | standard library. | morelisp wrote: | "First-party" is distinct from "first class". The | difference between a first-party library and the standard | library ranges from "slightly weaker compatibility | guarantees" to "it's supported in all environments where | it makes sense, but the language can run unhosted so | that's not everywhere" to "no difference at all, we just | didn't want to package it with the compiler". | | You're also missing the forest for the "well actually" | trees: Lots of languages have argument parsing in their | stdlib. | Groxx wrote: | as if there even was one command-line or config standard. | especially across different operating systems. | | it _absolutely_ does not belong in stdlibs, where it can | never be changed. that 's how you end up with too many | terrible CLI tools using Go's `flags` package. | MrStonedOne wrote: | php | qwerty2021 wrote: | >Command line argument parsing | | 20-30 LoC maybe. `process.argv.slice(2).forEach(str => ... | )`. | | there is no access to the raw command-line invocation, sadly, | so you really can't really do anything fancier than that. | | >config loading | | that thing "RC" package does - looking up the config file in | random locations - is really strange to me. aren't you the | one in control of where it is stored? | afavour wrote: | Just looking at the readme for Coa it's very obviously more | than the code you outlined. You're arguing against a | strawman here. | qwerty2021 wrote: | honestly, yeah. I just saw "command line parser" and | dismissed the rest as useless bloat | tyingq wrote: | Chained dependencies? If you can fool one popular package to | depend on you, you ride their coattails. | | And perhaps some faked download numbers to lend an air of | authenticity. | havkd wrote: | Maybe we need to hold the popular packages accountable for | stuff like this. | jart wrote: | Before we go mobbing innocent open source devs on Twitter, | it'd be great to know how far NPM has progressed on 2FA. Up | until 2018 NPM didn't have 2FA at all. They just introduced | it. It'd be nice if they could give some kind of progress | report on how widely adopted it's become. Ideally it should | be required for publishing packages. Or at the very least, | it'd be great to have some transparency about which package | authors are actually using it, who aren't, and who's | delegated their authorization to some other vendor like | Travis -- so we as users can make our own informed choices | about risk. It'd also be useful to have charts that log | dependency gravity over time since an important question in | situations like this is: did RC and Coa go from 70k to 17m | users yesterday? Or have they been established for a long | time? | madjam002 wrote: | This is like the third one this week right? | | I know people keep saying about post-install should be opt out | but then malware will just wait for first run instead. | | How about an option to refuse to install any packages that have | been published in the past week/2 weeks? That way hopefully | malware like this would have been spotted before you end up | running it locally. | m4rtink wrote: | They could also attack metadata parsers next - I don't think | those are very hardened right now. | ant6n wrote: | If everybody waits two weeks, then nobody will notice it on the | first two weeks. | greenyoda wrote: | See also the ongoing discussion about malware in "Coa", another | NPM package: https://news.ycombinator.com/item?id=29116878 | dang wrote: | Also recent: | | _NPM package 'ua-parser-JS' with more than 7M weekly download | is compromised_ - https://news.ycombinator.com/item?id=28962168 | - Oct 2021 (141 comments) ___________________________________________________________________ (page generated 2021-11-05 23:00 UTC)