Prototype-polluting assignment¶
ID: js/prototype-polluting-assignment
Kind: path-problem
Security severity: 6.1
Severity: warning
Precision: high
Tags:
- security
- external/cwe/cwe-078
- external/cwe/cwe-079
- external/cwe/cwe-094
- external/cwe/cwe-400
- external/cwe/cwe-471
- external/cwe/cwe-915
Query suites:
- javascript-code-scanning.qls
- javascript-security-extended.qls
- javascript-security-and-quality.qls
Click to see the query in the CodeQL repository
Most JavaScript objects inherit the properties of the built-in Object.prototype
object. Prototype pollution is a type of vulnerability in which an attacker is able to modify Object.prototype
. Since most objects inherit from the compromised Object.prototype
object, the attacker can use this to tamper with the application logic, and often escalate to remote code execution or cross-site scripting.
One way to cause prototype pollution is by modifying an object obtained via a user-controlled property name. Most objects have a special __proto__
property that refers to Object.prototype
. An attacker can abuse this special property to trick the application into performing unintended modifications of Object.prototype
.
Recommendation¶
Use an associative data structure that is resilient to untrusted key values, such as a Map. In some cases, a prototype-less object created with Object.create(null) may be preferable.
Alternatively, restrict the computed property name so it can’t clash with a built-in property, either by prefixing it with a constant string, or by rejecting inputs that don’t conform to the expected format.
Example¶
In the example below, the untrusted value req.params.id
is used as the property name req.session.todos[id]
. If a malicious user passes in the ID value __proto__
, the variable items
will then refer to Object.prototype
. Finally, the modification of items
then allows the attacker to inject arbitrary properties onto Object.prototype
.
let express = require('express');
let app = express()
app.put('/todos/:id', (req, res) => {
let id = req.params.id;
let items = req.session.todos[id];
if (!items) {
items = req.session.todos[id] = {};
}
items[req.query.name] = req.query.text;
res.end(200);
});
One way to fix this is to use Map objects to associate key/value pairs instead of regular objects, as shown below:
let express = require('express');
let app = express()
app.put('/todos/:id', (req, res) => {
let id = req.params.id;
let items = req.session.todos.get(id);
if (!items) {
items = new Map();
req.sessions.todos.set(id, items);
}
items.set(req.query.name, req.query.text);
res.end(200);
});
Another way to fix it is to prevent the __proto__
property from being used as a key, as shown below:
let express = require('express');
let app = express()
app.put('/todos/:id', (req, res) => {
let id = req.params.id;
if (id === '__proto__' || id === 'constructor' || id === 'prototype') {
res.end(403);
return;
}
let items = req.session.todos[id];
if (!items) {
items = req.session.todos[id] = {};
}
items[req.query.name] = req.query.text;
res.end(200);
});