CVE-2021-23434: Access of Resource Using Incompatible Type ('Type Confusion') (CWE-843)
value: This affects the package object-path before 0.11.6. A type confusion vulnerability can lead to a bypass of CVE-2020-15256 when the path components used in the path parameter are arrays. In particular, the condition currentPath === '__proto__' returns false if currentPath is ['__proto__']. This is because the === operator returns always false when the type of the operands is different.
CWE description: The product allocates or initializes a resource such as a pointer, object, or variable using one type, but it later accesses that resource using a type that is incompatible with the original type.
CVSS v3 base score: 8.6
Published: 2021-08-27T17:15Z
Repository: https://github.com/mariocasciaro/object-path
Language: JavaScript
Files changed: index.js, test.js
Commit date: 2021-08-27 16:38:05 +0200
Commit message: Fix prototype pollution when path components are not strings

Diff: diff --git a/index.js b/index.js
index 2a0af82..13159bc 100644
--- a/index.js
+++ b/index.js
@@ -109,10 +109,13 @@
       }
       if (typeof path === 'string') {
         return set(obj, path.split('.').map(getKey), value, doNotReplace);
       }
       var currentPath = path[0];
+      if (typeof currentPath !== 'string' && typeof currentPath !== 'number') {
+        currentPath = String(currentPath)
+      }
       var currentValue = getShallowProperty(obj, currentPath);
       if (options.includeInheritedProps && (currentPath === '__proto__' ||
         (currentPath === 'constructor' && typeof currentValue === 'function'))) {
         throw new Error('For security reasons, object\'s magic properties cannot be set')
       }
diff --git a/test.js b/test.js
index c1503cc..18a002a 100644
--- a/test.js
+++ b/test.js
@@ -239,25 +239,36 @@ describe('set', function () {
 
   it('[security] should not set magic properties in default mode', function () {
     objectPath.set({}, '__proto__.injected', 'this is bad')
     expect(Object.prototype.injected).to.be.undefined
 
+    objectPath.set({}, [['__proto__'], 'injected'], 'this is bad')
+    expect(Object.prototype.injected).to.be.undefined
+
     function Clazz() {}
     Clazz.prototype.test = 'original'
 
     objectPath.set(new Clazz(), '__proto__.test', 'this is bad')
     expect(Clazz.prototype.test).to.be.equal('original')
 
+    objectPath.set(new Clazz(), [['__proto__'], 'test'], 'this is bad')
+    expect(Clazz.prototype.test).to.be.equal('original')
+
     objectPath.set(new Clazz(), 'constructor.prototype.test', 'this is bad')
     expect(Clazz.prototype.test).to.be.equal('original')
   })
 
   it('[security] should throw an exception if trying to set magic properties in inheritedProps mode', function () {
     expect(function() {objectPath.withInheritedProps.set({}, '__proto__.injected', 'this is bad')})
       .to.throw('For security reasons')
     expect(Object.prototype.injected).to.be.undefined
 
+    expect(function() {
+      objectPath.withInheritedProps.set({}, [['__proto__'], 'injected'], 'this is bad')
+      expect(Object.prototype.injected).to.be.undefined
+    }).to.throw('For security reasons')
+
     function Clazz() {}
     Clazz.prototype.test = 'original'
 
     expect(function() {objectPath.withInheritedProps.set(new Clazz(), '__proto__.test', 'this is bad')})
       .to.throw('For security reasons')
@@ -265,12 +276,15 @@ describe('set', function () {
 
     expect(function() {objectPath.withInheritedProps.set(new Clazz(), 'constructor.prototype.test', 'this is bad')})
       .to.throw('For security reasons')
     expect(Clazz.prototype.test).to.be.equal('original')
 
-    const obj = {}
-    expect(function() {objectPath.withInheritedProps.set(obj, 'constructor.prototype.injected', 'this is OK')})
+    expect(function() {objectPath.withInheritedProps.set({}, 'constructor.prototype.injected', 'this is OK')})
+      .to.throw('For security reasons')
+    expect(Object.prototype.injected).to.be.undefined
+
+    expect(function() {objectPath.withInheritedProps.set({}, [['constructor'], 'prototype', 'injected'], 'this is bad')})
       .to.throw('For security reasons')
     expect(Object.prototype.injected).to.be.undefined
   })
 })
 

Vulnerable code:     const obj = {}
    expect(function() {objectPath.withInheritedProps.set(obj, 'constructor.prototype.injected', 'this is OK')})
Fixed code:       if (typeof currentPath !== 'string' && typeof currentPath !== 'number') {
        currentPath = String(currentPath)
      }
    objectPath.set({}, [['__proto__'], 'injected'], 'this is bad')
    expect(Object.prototype.injected).to.be.undefined

    objectPath.set(new Clazz(), [['__proto__'], 'test'], 'this is bad')
    expect(Clazz.prototype.test).to.be.equal('original')

    expect(function() {
      objectPath.withInheritedProps.set({}, [['__proto__'], 'injected'], 'this is bad')
      expect(Object.prototype.injected).to.be.undefined
    }).to.throw('For security reasons')

    expect(function() {objectPath.withInheritedProps.set({}, 'constructor.prototype.injected', 'this is OK')})
      .to.throw('For security reasons')
    expect(Object.prototype.injected).to.be.undefined

    expect(function() {objectPath.withInheritedProps.set({}, [['constructor'], 'prototype', 'injected'], 'this is bad')})