CVE-2021-23470: Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution') (CWE-1321)
value: This affects the package putil-merge before 3.8.0. The merge() function does not check the values passed into the argument. An attacker can supply a malicious value by adjusting the value to include the constructor property. Note: This vulnerability derives from an incomplete fix in https://security.snyk.io/vuln/SNYK-JS-PUTILMERGE-1317077
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: 9.8
Published: 2022-02-04T20:15Z
Repository: https://github.com/panates/putil-merge
Language: JavaScript
Files changed: lib/merge.js, package.json, test/merge.js
Commit date: 2022-02-02 16:21:34 +0300
Commit message: Fixed Prototype Pollution vulnerability (constructor). Thanks to Gill from Snyk Security

Diff: diff --git a/lib/merge.js b/lib/merge.js
index c1bd798..dc465ce 100644
--- a/lib/merge.js
+++ b/lib/merge.js
@@ -31,11 +31,11 @@ function merge(target, source, options = {}) {
 
   if (source === target) return target;
   const keys = Object.getOwnPropertyNames(source);
   keys.push(...Object.getOwnPropertySymbols(source));
   for (const key of keys) {
-    if (key === '__proto__')
+    if (key === '__proto__' || key === 'constructor')
       continue;
     if (options.filter && !options.filter(source, key))
       continue;
     if ((options.combine || options.adjunct) && target.hasOwnProperty(key))
       continue;
diff --git a/package.json b/package.json
index 67c44d1..6fb1340 100644
--- a/package.json
+++ b/package.json
@@ -17,13 +17,13 @@
     "javascript",
     "merge",
     "object"
   ],
   "devDependencies": {
-    "eslint": "^7.19.0",
+    "eslint": "^8.8.0",
     "eslint-config-google": "^0.14.0",
-    "mocha": "^9.0.2",
+    "mocha": "^9.2.0",
     "nyc": "^15.1.0"
   },
   "engines": {
     "node": ">= 10.0"
   },
diff --git a/test/merge.js b/test/merge.js
index 11f30e8..3fa3ae9 100644
--- a/test/merge.js
+++ b/test/merge.js
@@ -235,13 +235,20 @@ describe('merge', function() {
           c: 3
         }
     );
   });
 
-  it('should prevent Prototype Pollution vulnerability', function() {
+  it('should prevent Prototype Pollution vulnerability (__proto__)', function() {
     const payload = JSON.parse('{"__proto__":{"polluted":"Yes! Its Polluted"}}');
     const obj = {};
     merge(obj, payload, {deep: true});
     assert.strictEqual(obj.polluted, undefined);
   });
 
+  it('should prevent Prototype Pollution vulnerability (constructor)', function() {
+    const payload = JSON.parse('{"constructor": {"prototype": {"polluted": "yes"}}}');
+    let obj = {};
+    merge(obj, payload, {deep: true});
+    assert.strictEqual(obj.polluted, undefined);
+  });
+
 });

Vulnerable code:     if (key === '__proto__')
    "eslint": "^7.19.0",
    "mocha": "^9.0.2",
  it('should prevent Prototype Pollution vulnerability', function() {
Fixed code:     if (key === '__proto__' || key === 'constructor')
    "eslint": "^8.8.0",
    "mocha": "^9.2.0",
  it('should prevent Prototype Pollution vulnerability (__proto__)', function() {
  it('should prevent Prototype Pollution vulnerability (constructor)', function() {
    const payload = JSON.parse('{"constructor": {"prototype": {"polluted": "yes"}}}');
    let obj = {};
    merge(obj, payload, {deep: true});
    assert.strictEqual(obj.polluted, undefined);
  });