Description
Using external input to select an object and write its properties can reach a shared prototype through keys such as __proto__. Objects inheriting from the polluted prototype may expose unexpected properties, with security consequences depending on how application logic uses them.
Potential impact
- Unexpected behavior in features that use polluted properties
- Data corruption through altered processing or configuration
- Authentication or authorization bypass when decisions depend on polluted values
Remediation
- Validate keys used to select intermediate objects as well as the final field. Block
__proto__,constructor, andprototype. - Restrict writable fields to a server-owned allow-list. Do not trust an allow-list supplied in a request.
- Use
Object.hasOwnto distinguish inherited properties, and considerObject.create(null)orMapfor dictionary-like storage.
Examples
Before
javascript
app.get('/test/:id', (req, res) => {
let id = req.params.id;
let items = req.session.todos[id];
if (!items) {
items = req.session.todos[id] = {};
}
// Before: use external input directly as a key.
items[req.query.name] = req.query.text;
res.sendStatus(200);
});
After
javascript
app.get('/test/:id', (req, res) => {
const id = req.params.id;
const blockedKeys = new Set(['__proto__', 'constructor', 'prototype']);
const allowedKeys = new Set(['title', 'text', 'done']);
if (blockedKeys.has(id) || !allowedKeys.has(req.query.name)) {
return res.status(400).send('Invalid key name');
}
if (!Object.hasOwn(req.session.todos, id)) {
req.session.todos[id] = Object.create(null);
}
const items = req.session.todos[id];
items[req.query.name] = req.query.text;
res.sendStatus(200);
});
Explanation:
- Before: An
idof__proto__can select the shared prototype, and an unvalidated field name determines where the value is written. - After: Both the object-selection key and final field are checked. Inherited properties are not treated as existing entries. The example assumes the session's
todosstore has been initialized.