Prototype Pollution high vulnerability in ‘mixme’ NPM package

Dan Shallom

May 5, 2021

TL;DR

  • Learn about JavaScript Prototypes
  • Learn about Prototype Pollution
  • Introducing the Prototype Pollution vulnerability that OP Innovate discovered on mixme.
  • Mitigation & helpful tools and utilities.
  • https://nvd.nist.gov/vuln/detail/CVE-2021-28860 ; https://nvd.nist.gov/vuln/detail/CVE-2021-29491
  • https://www.npmjs.com/advisories/1668

NPM (Node Package Manager) is a gigantic software registry that contains hundreds of thousands of open source Node.js projects in the form of packages. As a matter of fact, if a developer wanted to share their code with the world, NPM would be a good way to do it.

If you read one of my previous articles entitled “DLL Injection Attack in Kerberos NPM package” you may recall a section describing the dangerous universe of open-source code. In short, open-source packages are often implemented without security in mind, potentially impacting the overall security of the consumers – large corporations or single users, it doesn’t really matter.

JavaScript Inheritance

Before we dive in, let’s remind ourselves of some JavaScript essentials. JavaScript is a very popular and well-known scripting language used in web pages, and notably for our current context, is a prototype-based programming language.

👉 What is a prototype? a prototype is an object and a property of an object. An object can have another object as its prototype. The base object can inherit all of its prototype’s attributes. The chain of objects connected by the prototype property is called Prototype Chain.

Figure 1 – prototypes

👉What is “__proto__”? At the time an object is created it gets the property of “__proto__”. ”__proto__” is an accessor property that points to the prototype object of the constructor function. “__proto__” is a way to inherit properties from an object in JavaScript.

Figure 2 – __proto__

For example, in this case the ModelX’s and ModelY’s proto will point to the same prototype object of the constructor function (Vehicle).

ModelX.__proto__ === ModelY.__proto__

The prototype object of the constructor function (in this case “Vehicle”) is shared by all the objects that were created by the constructor function (in this case “Vehicle”).

Put your protective masks on – it’s Prototype Pollution Time!

As we saw in the previous example, when an object accesses its “__proto__” it will point to the prototype object of the constructor function, meaning it will have access to the properties of the base class Vehicle (While Vehicle will inherit them from Vehicle’s prototype).

But what happens if we change an existing attribute or add a new one through the “__proto__” of an object? 

ModelX.__proto__.polluted = "Just got polluted !!"

The answer to this question is quite simple – every object within the running application will get the “polluted” attribute, and will be able to access the “polluted” attribute, directly – without needing to call the “proto”. In most cases the application will not be able to handle this situation. This will damage the application’s availability, leading it to crash. In other cases it will be possible to execute code. In short, the “polluted” attribute will affect the entire prototype chain!

But how can this dangerous situation happen in the wild?
A lot of packages out there use MERGE/SET-like operations. These kinds of functions usually take two parameters and assign one to the other. The big issue here is that in most cases these operations allow an attacker to intervene and affect the entire prototype chain by tampering or adding a new property to an object via “proto”.

Prototype Pollution in ‘mixme’ NPM package

‘Mixme’ package allows merging multiple objects recursively. It has 6 functions:
◼ merge(…data)
◼ mutate(…data)

◼ clone(data)
◼ is_object_literal(object)
◼ snake_case(object)
◼ compare(item_1, item_2)


We will examine the merge and mutate methods. The merge method is based mainly on the mutate function that is recursively implemented. It takes multiple parameters as an input, merges them and returns the merged value. Usually recursion is the best method to implement such utilities.
For example, this function call:

mutate({a: '1'}, {b: '2'})

Will output the following:

{a: '1', b: '2'}

Now let’s try something else – instead of merging vague parameters, we will try using the mutate function (MERGE operation) in a non-legitimate way. We’ll use two arguments for the mutate() function:

◼ Base object – {}
◼ Nested object – JSON.parse(‘{“proto“: {“polluted”: “Just polluted!!!”}}’)

JSON.parse will construct a JavaScript nested object:

{
     "__proto__":{
     "polluted":"Just polluted!!!"
     }
}

The POC:

const {merge} = require("C:\node_modules\mixme");
var base_obj = {}
console.log("Before MERGE: " + base_obj.polluted);
merge({}, JSON.parse('{"__proto__": {"polluted": "Just polluted!!!"}}'))
console.log("After MERGE: " + base_obj.polluted);
var obj_NEW =[]
console.log("obj_NEW also get the polluted attribute with the value of:" + obj_NEW.polluted)
  • Before the mutate() is called, the base object doesn’t contain the “polluted” attribute:
    • The value of base_obj.polluted is undefined.
  • But right after the MERGE operation, the base object and every other object in that runtime will have the “polluted” attribute:
    • The value of base_obj.polluted is “Just polluted!!!”.
    • Every object will now have the polluted attribute with the “Just polluted!!!” value. Following the MERGE operation, we created obj_NEW and printed the obj_NEW.polluted. The value is the same as base_obj.polluted (“Just polluted!!!”).

Figure 3 – console’s output

Let’s take a look over on object fields at the last iteration

Figure 4 – mutate() last iteration

Figure 5 – {}.__proto__.polluted=“Just polluted!!!”

So, basically this command –

merge({}, JSON.parse('{"__proto__": {"polluted": "Just polluted!!!"}}'))

Leads to the following assignment –

{}.__proto__.polluted="Just polluted!!!"

Prototype Pollution – mitigation

There are a couple of main points you should relate to when applying when considering a resolution:

💡Make sure to use safe recursive merge functions (harden ‘mutate’ function in this case)
💡Don’t allow the input to begin with “proto” field
💡Update 3rd party libraries and maintain a best practice policy for keeping them updated whenever an update is released

The following graph represent the number of downloads per day for ‘mixme’ package, during the last year:

Figure 6 – Taken from “npm-stat.com”

Takeaway

Prototype pollution is an underrated vulnerability that until recently didn’t get the proper attention. There are a lot of affected products that make use of insecure MERGE operations.

Written by Dan Shallom, Cyber security researcher | Certified Ethical Hacker (CEH) | Certified Security Analyst – Practical (ECSA)

Under Cyber Attack?

Fill out the form and we will contact you immediately.