ExploitedStream (Malicious JS file) Hackthebox Writeup

Vincent Andreas
5 min readSep 2, 2022

Challenge description:

An npm stream compromised dependencies long ago. Help us recover what this script is hiding.

This challenge is special, because it’s based on a real case, when the popular Node js package event-stream contained malicious code in its dependency, flatmap-stream. It happened, after the ownership of the repo moved to a suspicious github user. The malicious code contains encrypted string, that only can be decrypted using specific NPM package description. It turns out, the encrypted string successfully decrypted by using copay description, open source project for Bitcoin Wallet.

There’s a good video about this attack.

For this challenge, we got a javascript file. The file looks like this:

The file contains many operations that are hard to understand. The most interesting part is, the binary variable. It contains a long string. To understand more about this code, we need to de-obfuscate, and make the code more readable. After some time, the code looks like this.

We have decrypt function, that receive 3 parameters, _0x4450ef,paramAlgo,paramPasswd . Then, _global[“xor”] variable will refer to decrypt function.

In the last part of try scope, it will throw array, consist of key[“argv”][0x2]||o ,algorithm, _global,noxc. In the catch scope, we will call second index of Exception, to be loaded as function _0x59227e[0x2][“xor”](). we can see, that actually it calling decrypt function, with 3 parameters, (noxc,algo, key[‘argv’][0x2] || o). We already know the value of noxc, algo. But we don’t know yet, what is the value of key[“argv”][0x2].

If you have already watched that video before, you can see, there’s many similarities. Especially the createDecipher method, which is also being used here. Even the algorithm that used the same, aes-256-ctr.

Based on those codes, we can be sure that noxc variable will be the secret message / command that will be decrypted.If we analyze more, actually, the binary variable isn’t really used here. It’s just used for crafting the `o` variable. If we try to use the base64 decode for_future_me variable, we got the string “password_is_the_name_of_one_npm_package”.

Okay, so, we need to brute force using the npm package. There’s a great library that save npm packages name all-the-package-names, that is updated frequently. If we see on its github, names.json contains all of the names. Since the challenge is from 2019, we can search for commits that happened around nov 2019. It used to reduce brute force, and avoid the possibility of the correct package name being deleted in 2022.

names.json file looks like this

We can load this, as a list object.

Attempt #1

First, I think the package name will replace the new Date() in the o variable, since the date time will always change. I choose to use python, and for decrypting, I use openssl command, and for the noxc value, I save the value in enc_message.txt. The command for openssl will look like this.

cat enc_message.txt | xxd -r -ps | openssl enc -d -aes-256-ctr -pass “pass:+ payload +” 2> /dev/null

Explanation

xxd -r -ps are used for converting our hex string file to binary, Since openssl needs the binary one.

enc -d = decrypt mode.

-aes-256-ctr = algorithm for decrypting

-pass = for placing the password.

Since the openssl command is also deprecated when using key derivation we can remove the warning output to /dev/null.

The python code looks like this:

The code basically will iterate for every package name, and try to run that, using os.popen . openssl will output gibberish result. This is an example if we provide the wrong key:

Because of that, it will produce exceptions in python, so we can pass that part. If the correct package name appears, it will result in plaintext.

I try to run that script, but it’s not show the correct one, I try some combination, maybe password_length in here, also must be dynamic, not only 0x12.

So in the python script, I apply that in python too.

But it still doesn’t work.

If you already know more about using openssl, you already found which part that I’m wrong in openssl command.

Attempt #2

Since I’m still not able to decrypt that, I try to brute force using node.js. I realize, that, the result ofkey[‘argv’][0x2] || o will take o variable, if key[‘argv’][0x2] is undefined. but, if it defined, it will become the key, so, I try to directly using package name as the password. The code looks like this.

Since I don’t have node js in my machine, I use docker to run the node js in the container. Here is the docker file

To build docker & run that, I use this command

sudo docker build -q -t chall/expstream .

sudo docker run chall/expstream > outputResult

After the process finish, we can search the flag, using

cat outputResult | grep -a HTB

We got the flag!

What I’m wrong on openssl script

Since we already got the correct npm package name, I try to find again, what I’m wrong with the openssl command in that python script. And, I found some things that were wrong.

The main problem is, I don’t read the documentation of createDecipher. There’s clue, the encryption is using MD5 for digest algorithm, and with no salt.

So, we need to add -md md5 and -nosalt parameter into openssl script, so the correct script is this:

cat enc_message.txt | xxd -r -ps | openssl enc -d -aes-256-ctr -pass “pass:[payload]” -md md5 -nosalt 2> /dev/null

--

--