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?
Olivier Arteau published a complete white paper PDF called Prototype pollution attack in NodeJS application that covers identification and mitigation 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 objects 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.
It essentially works as a HashMap, but without all the security caveats that Object has. When a key/value structure is needed, Map should be preferred to Object.
It’s possible to create an object in JavaScript that don’t have any prototype. It requires the usage of the Object.create function. Objects created through this API won’t have the __proto__ and constructor attributes. Creating objects in this fashion can help mitigate
prototype pollution attacks.
let obj = Object.create(null);
obj.__proto__ // undefined
obj.constructor // undefined
Multiple libraries on npm (ex.: ajv ) offer schema validation for JSON data. Schema validation ensures that the JSON data contains all the expected attributes with the appropriate type. When using this approach to mitigate “prototype pollution” attacks, it’s important that unneeded attributes are rejected. In ajv, this can be done by setting additionalProperties to false on the schema.
Using Object.freeze will mitigate almost all exploitable cases.
Note that, while adding functions to the prototype of the base object is a frowned upon practice, it may still be used in your Node.js application or its dependencies. It’s highly recommend checking your Node.js application and its dependencies for such usage before going down this route. Since the behavior of frozen objects is to silently fail on property assignation, it may introduce hard to identify bugs.
Object.freeze(Object.prototype);
Object.freeze(Object);
({}).__proto__.test = 123;
({}).test // this will be undefined
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With