Remix.run Logo
DDerTyp 4 days ago

One of the most insidious parts of this malware's payload, which isn't getting enough attention, is how it chooses the replacement wallet address. It doesn't just pick one at random from its list.

It actually calculates the Levenshtein distance between the legitimate address and every address in its own list. It then selects the attacker's address that is visually most similar to the original one.

This is a brilliant piece of social engineering baked right into the code. It's designed to specifically defeat the common security habit of only checking the first and last few characters of an address before confirming a transaction.

We did a full deobfuscation of the payload and analyzed this specific function. Wrote up the details here for anyone interested: https://jdstaerk.substack.com/p/we-just-found-malicious-code...

Stay safe!

josefbud 4 days ago | parent | next [-]

I'm a little confused on one of the excerpts from your article.

> Our package-lock.json specified the stable version 1.3.2 or newer, so it installed the latest version 1.3.3

As far as I've always understood, the lockfile always specifies one single, locked version for each dependency, and even provides the URL to the tarball of that version. You can define "x version or newer" in the package.json file, but if it updates to a new patch version it's updating the lockfile with it. The npm docs suggest this is the case as well: https://arc.net/l/quote/cdigautx

And with that, packages usually shouldn't be getting updated in your CI pipeline.

Am I mistaken on how npm(/yarn/pnpm) lockfiles work?

sigotirandolas 4 days ago | parent | next [-]

Not the parent, but the default `npm install` / `yarn install` builds will ignore the lock file unless everything can be satisfied, if you want the lock file to be respected you must use `npm ci` / `yarn install --frozen-lockfile`.

In my experience, it's common for CI pipelines to be misconfigured in this way, and for Node developers to misunderstand what the lock file is for.

0cf8612b2e1e 4 days ago | parent | next [-]

Not a web guy, but that seems a bonkers default. I would have naively assumed a lockfile would be used unless explicitly ignored.

metafunctor 4 days ago | parent | next [-]

Welcome to the web side. Everything’s bonkers. Hard-earned software engineering truths get tossed out, because hey, wtf, I’ll just do some stuff and yippee. Feels like everyone’s stuck at year three of software engineering, and every three years the people get swapped out.

jiggawatts 4 days ago | parent | next [-]

> every three years the people get swapped out

That's because they are being "replaced", in a sense!

When an industry doubles every 5 years like web dev was for a long time, that by the mathematical definition means that the average developer has 5 years or less experience. Sure, the old guard eventually get to 10 or 15 years of experience, but they're simply outnumbered by an exponentially growing influx of total neophytes.

Hence the childish attitude and behaviour with everything to do with JavaScript.

metafunctor 4 days ago | parent [-]

Good point! The web is going through its own endless September.

And so, it seems, is everything else. Perhaps, this commentary adds no value — just old man yells at cloud stuff.

anonymars 3 days ago | parent | prev [-]

The web saw "worse is better" and said "hold my beer"

Already__Taken 4 days ago | parent | prev [-]

We didn't get locking until npm v5 (some memory and googling, could be wrong.) And it took a long time to do everything you'd think you want.

Changing the main command `npm install` after 7 years isn't really "stable". Anyway didn't this replace versions, so locking won't have helped either?

minitech 3 days ago | parent | next [-]

You can’t replace existing versions on npm. (But probably more important is what @jffry mentioned – yes, lockfiles include hashes.)

jffry 3 days ago | parent | prev [-]

> Anyway didn't this replace versions, so locking won't have helped either?

The lockfile includes a hash of the tarball, doesn't it?

Already__Taken 3 days ago | parent | next [-]

It does, the answer to my question was no.

thunderfork 3 days ago | parent | prev [-]

[dead]

DDerTyp 4 days ago | parent | prev | next [-]

TIL: I need to fix my CI pipeline. Gonna create a jira ticket I guess…

Thank you!

josefbud 4 days ago | parent [-]

Sorry, I had assumed this was what you were doing when I wrote my question but I should have specified. And sorry for now making your npm install step twice as long! ;)

rimunroe 4 days ago | parent [-]

npm ci should be much faster in CI as it can install the exact dependency versions directly from the lockfile rather than having to go through the whole dependency resolution algorithm. In CI environments you don't have to wait to delete a potentially large pre-existing node_modules directory since you should be starting fresh each time anyway.

josefbud 4 days ago | parent [-]

I've seen pipelines that cache node modules between runs to save time, but yeah if they're not doing that then you're totally right.

thunderfork 3 days ago | parent [-]

[dead]

josefbud 4 days ago | parent | prev [-]

Yeah, I think I had made the assumption that they were using `npm ci` / `yarn install --frozen-lockfile` / `pnpm install --frozen-lockfile` in CI because that's technically what you're always supposed to do in CI, but I shouldn't have made that assumption.

Mattwmaster58 4 days ago | parent | prev | next [-]

As others have noted, npm install can/will change your lockfile as it installs, and one caveat for the clean-install command they provide is that it is SLOW, since it deletes the entire node_modules directory. Lots of people have complained but they have done nothing: https://github.com/npm/cli/issues/564

The npm team eventually seemed to settle on requiring someone to bring an RFC for this improvment, and the RFC someone did create I think has sat neglected in a corner ever since.

saghm 4 days ago | parent [-]

Is there no flag to opt out of this behavior? For Rust, Cargo commands will also do this by default, but they also have `--offline` for not checking online for new versions, `--locked` to require sticking with the exact version of the lockfile even when allowing downloading dependencies online (e.g. if you're building on a machine that's never downloaded dependencies before, so they aren't cached locally, but you still don't want to allow implicit updates), and `--frozen` (which is a shorthand for both `--locked` and `--offline`). I'm honestly on the fence about whether this is even sufficient, since I've worked at multiple places where the CI didn't actually run with `--locked` because whoever configured it didn't realize, and at least once a surprise update to the lockfile in CI ended up causing an issue that took a bit of time to debug before someone realized what was going on.

DDerTyp 4 days ago | parent | prev [-]

You’re right and the excerpt you quoted was poorly worded and confusing. A lockfile is designed to do exactly what you said.

The package.json locked the file to ^1.3.2. If a newer version exists online that still satisfies the range in package.json (like 1.3.3 for ^1.3.2), npm install will often fetch that newer version and update your package-lock.json file automatically.

That’s how I understand it / that’s my current knowledge. Maybe there is someone here who can confirm/deny that. That would be great!

typpilol 3 days ago | parent [-]

You're correct

__MatrixMan__ 4 days ago | parent | prev | next [-]

We should be displaying hashes in a color scheme determined by the hash (foreground/background colors for each character determined by a hash of the hash, salted by that character's index, adjusted to ensure sufficient contrast).

That way it's much harder to make one hash look like another.

9dev 4 days ago | parent | next [-]

As someone with red/green vision deficiency: if you do this, please don’t forget people like me are unable to distinguish many shades of colours, which would be very disadvantageous here!

AaronAPU 4 days ago | parent | next [-]

It’s not like it would hurt you for there to be supplementary info others can see but you can’t.

gblargg 3 days ago | parent | next [-]

I think 9dev was saying that providing only a colorized version might make it unreadable to some people, not merely that they wouldn't benefit from the extra color information.

macintux 3 days ago | parent | prev [-]

And it's not like it would hurt the developers to be conscious of their choices.

zarzavat 3 days ago | parent [-]

There's actually nothing the developers can do about this particular issue other than to display all colors and allow colorblind people to see the colors that they can see.

__MatrixMan__ 3 days ago | parent | next [-]

It doesn't matter which colors the algorithm chooses so long as background/foreground are very distinguishable to as wide an audience as possible, and prev/next are likely to be distinguishable more often than not.

That's a lot of flexibility within which to do clever color math which accounts for the types of colorblindness according to their prevalence.

bbarnett 3 days ago | parent | prev [-]

For the newly made up feature, which doesn't exist yet, but already has an issue?

Simple. Instead of forcing colour, one could retain a no colour option maybe?

Done. Solved.

Everything should have this option. I personally have no colour vision issues, other than I find colour annoying in any output. There's a lot who prefer this too.

__MatrixMan__ 3 days ago | parent | next [-]

Agreed, although I would argue that maximal hash contrast should be default, and if people find they prefer less, they can turn it down.

If you're the sort of person who would think about adjusting it to suit your sensitivity to this kind of attack, you're likely not the sort of person that the feature is trying to protect anyhow.

3 days ago | parent | prev | next [-]
[deleted]
mdaniel 3 days ago | parent | prev [-]

Team https://no-color.org/ for life

One will not be surprised to see that Chalk chooses its own path via the stunningly opaque FORCE_COLOR=0 and is all :fu: to people who suggest otherwise <https://github.com/chalk/chalk/issues/547#issuecomment-11268...> One will especially enjoy the "get bent" response because I discovered that one issue by, you know, searching the issues <https://github.com/chalk/chalk/issues?q=is%3Aissue%20NO_COLO...>

__MatrixMan__ 4 days ago | parent | prev [-]

You could still ignore the colors and just read the characters, like people do now, and you could still use whatever color cues you are sensitive to.

Spivak 4 days ago | parent | prev [-]

Not sure why you're being downvoted, OpenSSH implemented randomart which gives you a little ascii "picture" of your key to make it easier for humans to validate. I have no idea if your scheme for producing keyart would work but it sounds like it would make a color "barcode".

Macha 3 days ago | parent | next [-]

I have to say the openssh random art has never really helped for me - I see each individual example so infrequently and there's so little detail to remember that it may as well just be a hash for all the memorability it doesn't add

__MatrixMan__ 4 days ago | parent | prev [-]

If you ignored the characters and just focused on the background colors, yeah I suppose it would look like a barcode. But the way I envision it, each line on the barcode is a character, so it still copy/pastes into notepad as the original text, but it'll copy/paste into word as colored text with colored background.

bflesch 4 days ago | parent | prev | next [-]

Can you attribute this technique to a specific group?

suzzer99 4 days ago | parent | next [-]

A few years ago, I remember reading about some NFT contract attack that did something similar. So I'm sure it's out there now.

illegally 3 days ago | parent | prev | next [-]

It's not a "group specific" technique.

This is smart, but not really unusual.

pants2 4 days ago | parent | prev [-]

Almost certainly Lazarus

sflanagain 4 days ago | parent [-]

The phishing email comes across a bit too amateur. Specifically the inclusion of:

"we kindly ask that you complete this update your earliest convenience".

The email was included here: https://cdn.prod.website-files.com/642adcaf364024654c71df23/...

From this article: https://www.aikido.dev/blog/npm-debug-and-chalk-packages-com...

rurban 3 days ago | parent | next [-]

Very amateur. Who would fall that, really? I can only suspect npm people who are used to unprofessional repo hosting practices.

Such a Two Factor Authentication update request would have needed a blog post first, to announce such a fishy request.

huflungdung 4 days ago | parent | prev [-]

[dead]

3abiton 3 days ago | parent | prev | next [-]

That moment where you respect the hacker. Still we are encroaching on dark times.

oasisbob 4 days ago | parent | prev [-]

> This is a brilliant piece of social engineering baked right into the code. It's designed to specifically defeat the common security habit ...

I don't agree that the exuberance over the brilliance of this attack is warranted if you give this a moment's thought. The web has been fighting lookalike attacks for decades. This is just a more dynamic version of the same.

To be honest, this whole post has the ring of AI writing, not careful analysis.

NoahZuniga 4 days ago | parent | next [-]

> To be honest, this whole post has the ring of AI writing, not careful analysis.

No it doesn't?

withinboredom 4 days ago | parent | prev | next [-]

> To be honest, this whole post has the ring of AI writing, not careful analysis.

It has been what, hours? since the discovery? Are you expecting them to spend time analysing it instead of announcing it?

Also, nearly everyone has AI editing content these days. It doesn’t mean it wasn’t written by a human.

bbarnett 3 days ago | parent [-]

Just for a counter, "nearly everyone" seems wildly ambitious.

I want no part of AI in any form of my communication, and I know many which espouse the same.

I will certainly agree on "many", but not "nearly everyone".

blueflow 4 days ago | parent | prev [-]

I've been thinking about using Levenshtein to make hexadecimal strings look more similar. Levenshtein might be useful for correcting typos, but not so when comparing hashes (specifically the start or end sections of it). Kinda odd.