javascript.lang.security.audit.prototype-pollution.prototype-pollution-loop.prototype-pollution-loop

profile photo of semgrepsemgrep
Author
123
Download Count*

Possibility of prototype polluting function detected. By adding or modifying attributes of an object prototype, it is possible to create attributes that exist on every object, or replace critical attributes with malicious ones. This can be problematic if the software depends on existence or non-existence of certain attributes, or uses pre-defined attributes of object prototype (such as hasOwnProperty, toString or valueOf). Possible mitigations might be: freezing the object prototype, using an object without prototypes (via Object.create(null) ), blocking modifications of attributes that resolve to object prototype, using Map instead of object.

Run Locally

Run in CI

Defintion

rules:
  - id: prototype-pollution-loop
    message: "Possibility of prototype polluting function detected. By adding or
      modifying attributes of an object prototype, it is possible to create
      attributes that exist on every object, or replace critical attributes with
      malicious ones. This can be problematic if the software depends on
      existence or non-existence of certain attributes, or uses pre-defined
      attributes of object prototype (such as hasOwnProperty, toString or
      valueOf). Possible mitigations might be: freezing the object prototype,
      using an object without prototypes (via Object.create(null) ), blocking
      modifications of attributes that resolve to object prototype, using Map
      instead of object."
    metadata:
      cwe:
        - "CWE-915: Improperly Controlled Modification of Dynamically-Determined
          Object Attributes"
      category: security
      references:
        - https://github.com/HoLyVieR/prototype-pollution-nsec18/blob/master/paper/JavaScript_prototype_pollution_attack_in_NodeJS.pdf
      technology:
        - typescript
      owasp:
        - A08:2021 - Software and Data Integrity Failures
      subcategory:
        - audit
      likelihood: LOW
      impact: LOW
      confidence: LOW
      license: Commons Clause License Condition v1.0[LGPL-2.1-only]
      vulnerability_class:
        - Mass Assignment
    languages:
      - typescript
      - javascript
    severity: WARNING
    patterns:
      - pattern-either:
          - pattern: |
              $SMTH = $SMTH[$A]
          - pattern: |
              $SMTH = $SMTH[$A] = ...
          - pattern: |
              $SMTH = $SMTH[$A] && $Z
          - pattern: |
              $SMTH = $SMTH[$A] || $Z
      - pattern-either:
          - pattern-inside: |
              for(...) {
                ...
              }
          - pattern-inside: |
              while(...) {
                ...
              }
          - pattern-inside: |
              $X.forEach(function $NAME(...) {
                ...
              })
      - pattern-not-inside: |
          for(var $A = $S; ...; ...) {...}
      - pattern-not-inside: |
          for($A = $S; ...; ...) {...}
      - pattern-not-inside: |
          $X.forEach(function $NAME($OBJ, $A,...) {...})
      - metavariable-pattern:
          patterns:
            - pattern-not: '"..."'
            - pattern-not: |
                `...${...}...`
            - pattern-not: |
                ($A: float)
          metavariable: $A

Examples

prototype-pollution-loop.js

function test1(name, value) {
  if (name.indexOf('.') === -1) {
    this.config[name] = value;
    return this;
  }
  let config = this.config;
  name = name.split('.');

  const length = name.length;
  name.forEach((item, index) => {
    if (index === length - 1) {
      config[item] = value;
    } else {
      if (!helper.isObject(config[item])) {
        config[item] = {};
      }
      // ruleid:prototype-pollution-loop
      config = config[item];
    }
  });
  return this;
}

function test2(obj, props, value) {
  if (typeof props == 'string') {
    props = props.split('.');
  }
  if (typeof props == 'symbol') {
    props = [props];
  }
  var lastProp = props.pop();
  if (!lastProp) {
    return false;
  }
  var thisProp;
  while ((thisProp = props.shift())) {
    if (typeof obj[thisProp] == 'undefined') {
      obj[thisProp] = {};
    }
    // ruleid:prototype-pollution-loop
    obj = obj[thisProp];
    if (!obj || typeof obj != 'object') {
      return false;
    }
  }
  obj[lastProp] = value;
  return true;
}

function test3(obj, prop, val) {
  const segs = split(prop);
  const last = segs.pop();
  while (segs.length) {
    const key = segs.shift();
    // ruleid:prototype-pollution-loop
    obj = obj[key] || (obj[key] = {});
  }
  obj[last] = val;
}

function okTest1(name) {
  if (name.indexOf('.') === -1) {
    this.config[name] = value;
    return this;
  }
  let config = this.config;
  name = name.split('.');

  const length = name.length;
  name.forEach((item, index) => {
    // ok:prototype-pollution-loop
    config = config[index];
  });
  return this;
}

function okTest2(name) {
  let config = this.config;
  name = name.split('.');

  const length = name.length;
  for (let i = 0; i < name.length; i++) {
    // ok:prototype-pollution-loop
    config = config[i];
  }
  return this;
}