Prototype pollution

Prototype pollution

Description

When user input controls a nested property path, following __proto__ or constructor.prototype can write to the prototype itself. Polluting a shared prototype may make attacker-supplied properties appear on other objects that inherit from it.

Potential impact

  • Application logic that reads polluted properties may behave unexpectedly.
  • Authentication or authorization checks may be bypassed if they rely on those properties.
  • Modified functions or configuration values may cause failures or data corruption.

Remediation

  • Check that every path component is a string, and block __proto__, constructor, and prototype.
  • Restrict writable fields with a server-defined allow-list and avoid following inherited properties during traversal.
  • Consider Object.create(null) or Map for dictionary-like storage.
  • Apply security patches to libraries such as Lodash or dot-prop. Using a library does not replace input validation.

Examples

Before

javascript
// Before: unrestricted dynamic property paths.
function setDeepValue(obj, keys, value) {
  keys = Array.isArray(keys) ? keys : keys.split('.')
  let cur = obj
  while (keys.length > 1) {
    const key = keys.shift()
    // Before: continue even when key is '__proto__'.
    if (!cur[key]) cur[key] = {}
    cur = cur[key]
  }
  cur[keys[0]] = value
}
// setDeepValue(user, ['__proto__', 'isAdmin'], true);

After

javascript
// After: validate path components and use own properties.
function setDeepValueSafe(obj, keys, value) {
  const blockedKeys = ['__proto__', 'constructor', 'prototype']
  keys = Array.isArray(keys) ? [...keys] : keys.split('.')
  if (keys.length === 0 || keys.some(key =>
      typeof key !== 'string' || blockedKeys.includes(key))) {
    throw new Error('위험한 속성명입니다.')
  }
  let cur = obj
  while (keys.length > 1) {
    const key = keys.shift()
    if (!Object.hasOwn(cur, key)) cur[key] = Object.create(null)
    cur = cur[key]
    if (cur === null || typeof cur !== 'object') {
      throw new Error('위험한 속성명입니다.')
    }
  }
  cur[keys[0]] = value
}

Explanation:

  • Before: Unrestricted traversal can reach a shared prototype through paths such as __proto__ and write to it.
  • After: Validate component types and dangerous keys first, then follow only the object's own properties at intermediate steps. New containers have no prototype.

References