Prototype pollution

Prototype pollution

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, and prototype.
  • Restrict writable fields to a server-owned allow-list. Do not trust an allow-list supplied in a request.
  • Use Object.hasOwn to distinguish inherited properties, and consider Object.create(null) or Map for 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 id of __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 todos store has been initialized.

References