5

Recently I stumbled across a vulnerability in doT.js. The vulnerability exists because attackers can use prototype pollution to modify the values of the options passed in to doT.

Example:

var doT = require("dot");
var tempFn = doT.template("<h1>Here is a sample template " +
    "{{=console.log(23)}}</h1>");
tempFn({})
var doT = require("dot"); // prototype pollution attack vector
Object.prototype.templateSettings = {varname:"a,b,c,d,x=console.log(25)"};
// benign looking template compilation + application
var dots = require("dot").process({path: "./resources"});
dots.mytemplate();

Then I got to thinking: doesn't this mean that virtually any JavaScript library's API options can be compromised through prototype pollution?

For example, here's express.static used with options.

var options = {
  dotfiles: 'ignore',
  etag: false,
  extensions: ['htm', 'html'],
  index: false,
  maxAge: '1d',
  redirect: false,
  setHeaders: function (res, path, stat) {
    res.set('x-timestamp', Date.now())
  }
}

app.use(express.static('public', options))

Couldn't an attacker set Object.prototype.redirect = true, and, if unspecified by the user, a redirect would occur? And there are surely many more malicious use cases.

What can be done, as a library author, to allow passing in options but safeguard against prototype pollution?

EDIT: I'm focusing specifically on packages distributed with NPM. For example, what could the authors of doT.js do to resolve the vulnerability?

Ben Gubler
  • 1,393
  • 3
  • 18
  • 32
  • 3
    What is an attacker doing running their own Javascript on your server inside your server process? That's your main problem. If they can do that, they can do basically anything. That vulnerability is not just the prototype - it's all sorts of things. They can steal your certificates, your database credentials, credit card data, etc... – jfriend00 Sep 04 '19 at 03:11
  • Library authors can't protect against anything the coder using your library wants to do. Not only do they control the environment you run in, but they also control the source code of your own library. It seems like you're under a false impression about what a library author can protect against. – jfriend00 Sep 04 '19 at 03:21

3 Answers3

14

Olivier Arteau published a complete white paper PDF called Prototype pollution attack in NodeJS application that covers identification and mitigation of the attack.


General concept of the attack

The general idea behind prototype pollution starts with the fact the attacker has control over at least the parameter a and value of any expression of the following form:

obj[a][b] = value;

The attacker can set a to __proto__ and the property with the name defined by b will be defined on all existing object (of the class of obj) of the application with the value value.

The same thing can append with the following form when the attacker has at least control of a, b and value.

obj[a][b][c] = value;

The attacker can set a to constructor, b to prototype and the property with the name defined by c will be defined on all existing object of the application with the value value.

However since this requires more complex object assignment, the first form is easier to work with.

While, it’s pretty rare that you will stumble on code that looks textually like the example provided, some manipulation can provide the attacker with similar control.

Mitigation

Using Map instead of Object

It essentially works as a HashMap, but without all the security caveats that Object have. When a key/value structure is needed, Map should be preferred to Object.

Object.create(null)

It’s possible to create object in JavaScript that don’t have any prototype. It requires the usage of the Object.create function. Object created through this API won’t have the __proto__ and constructor attributes. Creating object in this fashion can help mitigate prototype pollution attack.

let obj = Object.create(null);
obj.__proto__ // undefined
obj.constructor // undefined

Schema validation of JSON input

Multiple library on npm (ex.: ajv ) offer schema validation for JSON data. Schema validation ensure that the JSON data contains all the expected attributes with the appropriate type. When using this approach to mitigate “prototype pollution” attack, it’s important that unneeded attributes are rejected. In ajv, this can be done by setting additionalProperties to false on the schema.

Freezing the prototype

Using Object.freeze will mitigate almost all the exploitable case.

Note that while, adding function to the prototype of the base object is a frown upon practice, it may still be used in your Node.js application or its dependency. It’s highly recommend checking your Node.js application and its dependency for such usage before going down this route. Since the behavior of frozen object is to silently fail on property assignation, it may introduce hard to identify bug.

Object.freeze(Object.prototype);
Object.freeze(Object);
({}).__proto__.test = 123;
({}).test // this will be undefined
str
  • 42,689
  • 17
  • 109
  • 127
Yves M.
  • 29,855
  • 23
  • 108
  • 144
  • Great answer! Question, is it also a solid mitigation to remove `__proto__` and `constructor` properties from objects you don't trust? – Otto Jun 04 '21 at 07:34
  • What kind of security mitigation offer the Map if it is susceptible to prototype tampering in the same way as any JavaScript object is: const get = Map.prototype.get; Map.prototype.get = function (key, val) { return get.call(this, key, 'fake') }; – n-- Dec 06 '21 at 11:21
2

What can be done, as a library author, to allow passing in options but safeguard against prototype pollution?

You can detect whether a property is on your actual object or inherited via the prototype with .hasOwnProperty(). But, heck an attacker could overwrite .hasOwnProperty() too and change its behavior.

As I've said in the comments, someone using your library in their Javascript program has FULL source code access to your code. So, they don't even have to use prototype pollution to modify things - they can just hack away at your code however they want.

To fully protect your code, you'd have to either distribute only a compiled executable that runs in a different process and has an interprocess API (such as an http server) or you'd have to put your code into a service and only offer access that way. If you're distributing a Javascript library, by its very nature, you have to distribute source so the programmer using your library can really do anything they want to it. They don't even have to resort to prototype trickery.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • Right now, I'm distributing my library on NPM, and doT is distributed on NPM as well. Does this mean that doT's vulnerability isn't actually a vulnerability? I think maybe part of the problem is that other secretly malicious NPM packages could potentially use prototype pollution to change doT's settings. But then again, another NPM package could run any code it wants anyways... so how is the vulnerability I linked to in doT.js even a valid vulnerability? – Ben Gubler Sep 04 '19 at 12:17
  • 2
    @BenGubler - As I've already said, any attacker that could carry out the doT.js attack on your server could do far, far worse things on your server. You HAVE to only run trusted libraries on your server. I don't see how this is anything to worry about. The link you provided just shows that the report was "closed", but does not have any discussion about why it was closed. I presume it was closed because it isn't really a valid vulnerability. – jfriend00 Sep 04 '19 at 16:04
-2

I had this issue as well and upgraded my package.json dependencies to the latest versions packages might be outdated. Sometimes it maybe something as small as an upgrade or downgrade.

npm install -g npm-check-updates

Running the command npm audit will give you report of dependency vulnerabilities and suggested patches.

 npm audit
Nicholas
  • 2,800
  • 1
  • 14
  • 21
concepttech
  • 17
  • 2
  • 9
  • `npm audit` is great, but I don't this answers the question. They're asking how library *authors* can prevent prototype pollution. This is only helpful for people using the libraries. – Andre OBrien Jan 17 '23 at 21:33