CVE-2021-25952: Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution') (CWE-1321)
value: Prototype pollution vulnerability in ‘just-safe-set’ versions 1.0.0 through 2.2.1 allows an attacker to cause a denial of service and may lead to remote code execution.
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: 2021-07-07T12:15Z
Repository: https://github.com/angus-c/just
Language: JavaScript
Files changed: packages/object-safe-set/index.js, packages/object-safe-set/package.json, test/object-safe-set/index.js
Commit date: 2021-05-08 12:44:36 -0700
Commit message: add proto check (#267)


Diff: diff --git a/packages/object-safe-set/index.js b/packages/object-safe-set/index.js
index 9ae36de..8387c49 100644
--- a/packages/object-safe-set/index.js
+++ b/packages/object-safe-set/index.js
@@ -32,12 +32,14 @@ function set(obj, props, value) {
   }
   var lastProp = props.pop();
   if (!lastProp) {
     return false;
   }
+  prototypeCheck(lastProp);
   var thisProp;
   while ((thisProp = props.shift())) {
+    prototypeCheck(thisProp);
     if (typeof obj[thisProp] == 'undefined') {
       obj[thisProp] = {};
     }
     obj = obj[thisProp];
     if (!obj || typeof obj != 'object') {
@@ -45,5 +47,11 @@ function set(obj, props, value) {
     }
   }
   obj[lastProp] = value;
   return true;
 }
+
+function prototypeCheck(prop) {
+  if (prop === '__proto__' || prop === 'constructor' || prop === 'prototype') {
+    throw new Error('setting of prototype values not supported');
+  }
+}
diff --git a/packages/object-safe-set/package.json b/packages/object-safe-set/package.json
index e69bc60..0ea8823 100644
--- a/packages/object-safe-set/package.json
+++ b/packages/object-safe-set/package.json
@@ -1,8 +1,8 @@
 {
   "name": "just-safe-set",
-  "version": "2.2.1",
+  "version": "2.2.2",
   "description": "set value at property, create intermediate properties if necessary",
   "main": "index.js",
   "types": "index.d.ts",
   "scripts": {
     "test": "echo \"Error: no test specified\" && exit 1"
diff --git a/test/object-safe-set/index.js b/test/object-safe-set/index.js
index ea92f98..b1c289f 100644
--- a/test/object-safe-set/index.js
+++ b/test/object-safe-set/index.js
@@ -64,10 +64,31 @@ test("doesn't interrupt property chain, using array arg", function(t) {
   var obj2 = {a: {aa: 9}};
   t.isEqual(set(obj2, ['a', 'aa'], {bbb: 7}), true);
   t.end();
 });
 
+test("doesn't support setting of prototype (and related) values", function(t) {
+  t.plan(4);
+  t.throws(function() {
+    var obj1 = {a: {}};
+    set(obj1, '__proto__.x', function malice() {});
+  });
+  t.throws(function() {
+    var obj1 = {a: {}};
+    set(obj1, ['a', 'b', '__proto__'], {toString: 'hehehe'});
+  });
+  t.throws(function() {
+    var obj2 = {a: {}};
+    set(obj2, 'constructor', function FakeConstructor() {});
+  });
+  t.throws(function() {
+    var obj3 = {a: {}};
+    set(obj3, 'prototype.y', 'hahahaha');
+  });
+  t.end();
+});
+
 /* eslint-disable no-undef*/
 if (typeof Symbol === 'function') {
   test('supports symbol prop', function(t) {
     t.plan(2);
     var obj1 = {a: {}};

Vulnerable code:   "version": "2.2.1",
Fixed code:   prototypeCheck(lastProp);
    prototypeCheck(thisProp);

function prototypeCheck(prop) {
  if (prop === '__proto__' || prop === 'constructor' || prop === 'prototype') {
    throw new Error('setting of prototype values not supported');
  }
}
  "version": "2.2.2",
test("doesn't support setting of prototype (and related) values", function(t) {
  t.plan(4);
  t.throws(function() {
    var obj1 = {a: {}};
    set(obj1, '__proto__.x', function malice() {});
  });
  t.throws(function() {
    var obj1 = {a: {}};
    set(obj1, ['a', 'b', '__proto__'], {toString: 'hehehe'});
  });
  t.throws(function() {
    var obj2 = {a: {}};
    set(obj2, 'constructor', function FakeConstructor() {});
  });
  t.throws(function() {
    var obj3 = {a: {}};
    set(obj3, 'prototype.y', 'hahahaha');
  });
  t.end();
});