Coordinated Disclosure Timeline

Summary

Merge-deep actively attempts to prevent prototype pollution by blocking object property merges into __proto__, however it still allows for prototype pollution of Object.prototype via a constructor payload.

Product

merge-deep 3.0.2 (latest version)

Tested Version

3.0.2

Details

merge-deep can be tricked into overwriting properties of Object.prototype or adding new properties to it. These properties are then inherited by every object in the program.

Here is an example, in which we assume that payload is provided by an attacker, and isAdmin is a property that is later on used to check whether a user is authorized to perform some sensitive operation:

var mergeDeep = require("merge-deep");
var payload = '{"constructor": {"prototype": {"isAdmin": true}}}';
mergeDeep({}, JSON.parse(payload));

After this code has run, Object.prototype.isAdmin is true. But Object.prototype is on the prototype chain of most objects, so we also have {}.isAdmin == true, and indeed u.isAdmin == true for most other objects u, including, perhaps, objects used to represent users.

It is important to note that the above code snippet throws an exception (since Object.prototype cannot be reassigned), but it still sets the property (since it can be mutated). In some settings this might make the vulnerability difficult to exploit, but in others (for example web servers, which generally are written to be tolerant to route-handler crashes) it will still be easily exploitable.

Impact

JavaScript prototype pollution can lead to a variety of application context specific impacts ranging from Denial of Service (DoS) to Remote Code Execution (RCE).

Credit

This issue was discovered and reported by GitHub team member @max-schaefer.

Contact

You can contact the GHSL team at securitylab@github.com, please include GHSL-2020-160 in any communication regarding this issue.