Background

In July 2019, code was added to an upstream of the purescript npm installer which sabotaged the completion of the installation process in a particularly interesting way. The subterfuge was particularly difficult to uncover & debug, taking 5 days for some of the most talented and skilled npmjs devs to unravel. It would be fair to say that this event “stumped” (their words, not mine) some of the most talented and well versed engineers in this space. It’s my favourite dep malware, not because it resulted in some epic hack, but because it was so well executed by somebody with an innate understanding of the downstream landscape. It’s a great thought exercise into the potential power of insider threats and also upstreams.

The malware takes advantage of how downstream projects load in the affected code, it uses conditional exploitation logic to hamper debug efforts, and uses just the right level of obfuscation to not immediately look suspect (no super sus looking B64’d blobs here). The malicious software also cleans up after itself.

In this post I hope to step through just how all this took place, so maybe we can all be impressed and together. This particular event got very little coverage, so hopefully you find reading about it notable even though it was a little while ago.

My colleagues really like it when I annotate malware for them, particularly side-by-side, so here’s an attempt at posting in that format.

Timeline

July 5 1pm UTC:

PureScript-0.13.2 released. Compiler maintainers are able to successfully install the project using the npm installer.

July 5 9pm UTC:

A new version of load-from-cwd-or-npm is published (3.0.2) containing exploit version 1 (listed below). Bug reports beging stating that users cannot install purescript. Maintainers have trouble reproducing the bug, as it won’t reliably reproduce and won’t occur during local checkouts.

July 9 1a UTC:

A user identifies that the bug is being caused by load-from-cwd-or-npm@3.0.2 and opens an issue asking them to fix. The issue is not determined to be malicious at this time. The issue is deleted by the maintainer of load-from-cwd-or-npm@3.0.2.

July 9 5a UTC:

A new version of load-from-cwd-or-npm is published that doesn’t contain an exploit.

July 9 8a UTC:

rate-map@1.0.3 is published which is basically a more advanced version of the load-from-cwd-or-npm exploit.

July 9 11a UTC:

Maintainers still do not suspect any bad faith from the malicious upstreams.

July 9 1130a UTC:

Maintainer hdgarrood spots the malicious code and that this is a deliberately malicious act.

July 9 2p UTC:

A new version of PureScript is released that drops all dependency on the rate-map & load-from-cwd-or-npm.

What you’re looking at / code intent

The samples below both sabotage the purescript npm installer process to prevent it running successfully under certain conditions.

The Details: exploit version 1 load-from-cwd-or-npm@3.0.2

You can grab a copy of the dependency off of my repo here: load-from-cwd-or-npm@3.0.2

Load up a editor and open load-from-cwd-or-npm@3.0.2 index.js file.

Check out lines 50 - 83: - I am going to #annotate the below snippet for you, watch out for the comments (#) in the code snippet. The below code is designed to determine the environment it is running in, and decide what action to take.

pic of load-from-cwd-or-npm

The Details: exploit version 2 rate-map@1.0.3

You can grab a copy of the dependency off of my repo here: rate-map@1.0.3

Things get a little spicier from this point on. This version of the exploit contains the same do-while loop decision gate on exploitation, but it includes some significant upgrades.

pic of rate-map

Final Notes

I intentionally skipped over some background information surrounding the disagreements between authors of purescript and load-from-cwd-or-npm etc. I just think the techniques used particularly in rate-map@1.0.3 are great uses of light (but effective) obfuscation and conditional exploitation.