CVE-2018-3721: Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution') (CWE-1321)
value: lodash node module before 4.17.5 suffers from a Modification of Assumed-Immutable Data (MAID) vulnerability via defaultsDeep, merge, and mergeWith functions, which allows a malicious user to modify the prototype of "Object" via __proto__, causing the addition or modification of an existing property that will exist on all objects.
CWE description: The product receives input from an upstream component that specifies attributes that are to be initialized or updated in an object, but it does not properly control modifications of attributes of the object prototype.
CVSS v3 base score: 6.5
Published: 2018-06-07T02:29Z
Repository: https://github.com/lodash/lodash
Language: JavaScript
Files changed: lodash.js, test/test.js
Commit date: 2018-01-30 23:21:12 -0800
Commit message: Avoid merging properties on to __proto__ objects.

Diff: diff --git a/lodash.js b/lodash.js
index b39ddce6..f8a1b96d 100644
--- a/lodash.js
+++ b/lodash.js
@@ -1243,10 +1243,24 @@
       }
     }
     return result;
   }
 
+  /**
+   * Gets the value at `key`, unless `key` is "__proto__".
+   *
+   * @private
+   * @param {Object} object The object to query.
+   * @param {string} key The key of the property to get.
+   * @returns {*} Returns the property value.
+   */
+  function safeGet(object, key) {
+    return key == '__proto__'
+      ? undefined
+      : object[key];
+  }
+
   /**
    * Converts `set` to an array of its values.
    *
    * @private
    * @param {Object} set The set to convert.
@@ -3613,11 +3627,11 @@
           stack || (stack = new Stack);
           baseMergeDeep(object, source, key, srcIndex, baseMerge, customizer, stack);
         }
         else {
           var newValue = customizer
-            ? customizer(object[key], srcValue, (key + ''), object, source, stack)
+            ? customizer(safeGet(object, key), srcValue, (key + ''), object, source, stack)
             : undefined;
 
           if (newValue === undefined) {
             newValue = srcValue;
           }
@@ -3640,12 +3654,12 @@
      * @param {Function} [customizer] The function to customize assigned values.
      * @param {Object} [stack] Tracks traversed source values and their merged
      *  counterparts.
      */
     function baseMergeDeep(object, source, key, srcIndex, mergeFunc, customizer, stack) {
-      var objValue = object[key],
-          srcValue = source[key],
+      var objValue = safeGet(object, key),
+          srcValue = safeGet(source, key),
           stacked = stack.get(srcValue);
 
       if (stacked) {
         assignMergeValue(object, key, stacked);
         return;
diff --git a/test/test.js b/test/test.js
index c75e2e92..a87c892c 100644
--- a/test/test.js
+++ b/test/test.js
@@ -7537,10 +7537,25 @@
       assert.deepEqual(actual, expected);
 
       actual = _.groupBy([{ 'a': '__proto__' }], 'a');
       assert.notOk(actual instanceof Array);
     });
+
+    QUnit.test('should not merge "__proto__" properties', function(assert) {
+      assert.expect(1);
+
+      if (JSON) {
+        _.merge({}, JSON.parse('{"__proto__":{"a":1}}'));
+
+        var actual = "a" in objectProto;
+        delete objectProto.a;
+
+        assert.notOk(actual);
+      } else {
+        skipAssert(assert);
+      }
+    });
   }());
 
   /*--------------------------------------------------------------------------*/
 
   QUnit.module('lodash.fromPairs');

Vulnerable code:             ? customizer(object[key], srcValue, (key + ''), object, source, stack)
      var objValue = object[key],
          srcValue = source[key],
Fixed code:   /**
   * Gets the value at `key`, unless `key` is "__proto__".
   *
   * @private
   * @param {Object} object The object to query.
   * @param {string} key The key of the property to get.
   * @returns {*} Returns the property value.
   */
  function safeGet(object, key) {
    return key == '__proto__'
      ? undefined
      : object[key];
  }

            ? customizer(safeGet(object, key), srcValue, (key + ''), object, source, stack)
      var objValue = safeGet(object, key),
          srcValue = safeGet(source, key),

    QUnit.test('should not merge "__proto__" properties', function(assert) {
      assert.expect(1);

      if (JSON) {
        _.merge({}, JSON.parse('{"__proto__":{"a":1}}'));

        var actual = "a" in objectProto;
        delete objectProto.a;

        assert.notOk(actual);
      } else {
        skipAssert(assert);
      }
    });