CVE-2020-35149: Insufficient Information (NVD-CWE-noinfo)
value: lib/utils.js in mquery before 3.2.3 allows a pollution attack because a special property (e.g., __proto__) can be copied during a merge or clone operation.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
CVSS v3 base score: 5.3
Published: 2020-12-11T19:15Z
Repository: https://github.com/aheckmann/mquery
Language: JavaScript
Files changed: lib/utils.js, test/utils.test.js
Commit date: 2020-12-10 15:24:30 -0500
Commit message: fix(utils): avoid copying special properties like `__proto__` when merging and cloning

Diff: diff --git a/lib/utils.js b/lib/utils.js
index ef51712..fba56c8 100644
--- a/lib/utils.js
+++ b/lib/utils.js
@@ -5,10 +5,12 @@
  */
 
 var Buffer = require('safe-buffer').Buffer;
 var RegExpClone = require('regexp-clone');
 
+var specialProperties = ['__proto__', 'constructor', 'prototype'];
+
 /**
  * Clones objects
  *
  * @param {Object} obj the object to clone
  * @param {Object} options
@@ -67,10 +69,16 @@ exports.cloneObject = function cloneObject(obj, options) {
   var hasKeys;
   var val;
   var k;
 
   for (k in obj) {
+    // Not technically prototype pollution because this wouldn't merge properties
+    // onto `Object.prototype`, but avoid properties like __proto__ as a precaution.
+    if (specialProperties.indexOf(k) !== -1) {
+      continue;
+    }
+
     val = clone(obj[k], options);
 
     if (!minimize || ('undefined' !== typeof val)) {
       hasKeys || (hasKeys = true);
       ret[k] = val;
@@ -131,10 +139,13 @@ exports.merge = function merge(to, from) {
       i = keys.length,
       key;
 
   while (i--) {
     key = keys[i];
+    if (specialProperties.indexOf(key) !== -1) {
+      continue;
+    }
     if ('undefined' === typeof to[key]) {
       to[key] = from[key];
     } else {
       if (exports.isObject(from[key])) {
         merge(to[key], from[key]);
diff --git a/test/utils.test.js b/test/utils.test.js
index ff95f33..949da4e 100644
--- a/test/utils.test.js
+++ b/test/utils.test.js
@@ -138,7 +138,25 @@ describe('lib/utils', function() {
         assert.equal(buff[i], clone[i]);
       }
 
       done();
     });
+
+    it('skips __proto__', function() {
+      var payload = JSON.parse('{"__proto__": {"polluted": "vulnerable"}}');
+      var res = utils.clone(payload);
+
+      assert.strictEqual({}.polluted, void 0);
+      assert.strictEqual(res.__proto__, Object.prototype);
+    });
+  });
+
+  describe('merge', function() {
+    it('avoids prototype pollution', function() {
+      var payload = JSON.parse('{"__proto__": {"polluted": "vulnerable"}}');
+      var obj = {};
+      utils.merge(obj, payload);
+
+      assert.strictEqual({}.polluted, void 0);
+    });
   });
 });

Fixed code: var specialProperties = ['__proto__', 'constructor', 'prototype'];

    // Not technically prototype pollution because this wouldn't merge properties
    // onto `Object.prototype`, but avoid properties like __proto__ as a precaution.
    if (specialProperties.indexOf(k) !== -1) {
      continue;
    }

    if (specialProperties.indexOf(key) !== -1) {
      continue;
    }

    it('skips __proto__', function() {
      var payload = JSON.parse('{"__proto__": {"polluted": "vulnerable"}}');
      var res = utils.clone(payload);

      assert.strictEqual({}.polluted, void 0);
      assert.strictEqual(res.__proto__, Object.prototype);
    });
  });

  describe('merge', function() {
    it('avoids prototype pollution', function() {
      var payload = JSON.parse('{"__proto__": {"polluted": "vulnerable"}}');
      var obj = {};
      utils.merge(obj, payload);

      assert.strictEqual({}.polluted, void 0);
    });