CVE-2020-7743: Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution') (CWE-1321)
value: The package mathjs before 7.5.1 are vulnerable to Prototype Pollution via the deepExtend function that runs upon configuration updates.
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: 7.3
Published: 2020-10-13T10:15Z
Repository: https://github.com/josdejong/mathjs
Language: JavaScript
Files changed: HISTORY.md, src/utils/object.js, test/unit-tests/expression/security.test.js, test/unit-tests/utils/object.test.js
Commit date: 2020-10-10 10:24:48 +0200
Commit message: Fix object pollution vulnerability in `math.config`

Diff: diff --git a/HISTORY.md b/HISTORY.md
index ce240300d..ef5ccb8aa 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -1,7 +1,12 @@
 # History
 
+# not yet published, version 7.5.1
+
+- Fix object pollution vulnerability in `math.config`. Thanks Snyk.
+
+
 # 2020-10-07, version 7.5.0
 
 - Function `pickRandom` now allows randomly picking elements from matrices 
   with 2 or more dimensions instead of only from a vector, see #1974.
   Thanks @KonradLinkowski.
diff --git a/src/utils/object.js b/src/utils/object.js
index 46cf4b3c7..9c3abf7a5 100644
--- a/src/utils/object.js
+++ b/src/utils/object.js
@@ -84,11 +84,13 @@ export function deepExtend (a, b) {
   if (Array.isArray(b)) {
     throw new TypeError('Arrays are not supported by deepExtend')
   }
 
   for (const prop in b) {
-    if (hasOwnProperty(b, prop)) {
+    // We check against prop not being in Object.prototype or Function.prototype
+    // to prevent polluting for example Object.__proto__.
+    if (hasOwnProperty(b, prop) && !(prop in Object.prototype) && !(prop in Function.prototype)) {
       if (b[prop] && b[prop].constructor === Object) {
         if (a[prop] === undefined) {
           a[prop] = {}
         }
         if (a[prop] && a[prop].constructor === Object) {
diff --git a/test/unit-tests/expression/security.test.js b/test/unit-tests/expression/security.test.js
index 1199f1aea..ce3c31eee 100644
--- a/test/unit-tests/expression/security.test.js
+++ b/test/unit-tests/expression/security.test.js
@@ -388,10 +388,38 @@ describe('security', function () {
     assert.strictEqual(math.expression.mathWithTransform.Matrix, undefined)
     assert.strictEqual(math.expression.mathWithTransform.Node, undefined)
     assert.strictEqual(math.expression.mathWithTransform.chain, undefined)
     assert.deepStrictEqual(math.evaluate('chain'), math.unit('chain'))
   })
+
+  it('should not allow polluting the Object prototype via config', () => {
+    const obj = {}
+    assert.strictEqual(obj.polluted, undefined)
+
+    // change the configuration
+    const newConfig = JSON.parse('{"__proto__":{"polluted":"yes"}}')
+    math.config(newConfig)
+    assert.strictEqual(obj.polluted, undefined)
+  })
+
+  it('should not allow polluting the Object prototype via config via the expression parser', () => {
+    const obj = {}
+    assert.strictEqual(obj.polluted, undefined)
+
+    // change the configuration
+    math.evaluate('config({"__proto__":{"polluted":"yes"}})')
+    assert.strictEqual(obj.polluted, undefined)
+  })
+
+  it('should not allow polluting the Object prototype by creating an object in the expression parser', () => {
+    const obj = {}
+    assert.strictEqual(obj.polluted, undefined)
+
+    // change the configuration
+    math.evaluate('a = {"__proto__":{"polluted":"yes"}}')
+    assert.strictEqual(obj.polluted, undefined)
+  })
 })
 
 function isPlainObject (object) {
   return typeof object === 'object' && object &&
       object.constructor === Object &&
diff --git a/test/unit-tests/utils/object.test.js b/test/unit-tests/utils/object.test.js
index 3da1b443f..e9b071029 100644
--- a/test/unit-tests/utils/object.test.js
+++ b/test/unit-tests/utils/object.test.js
@@ -139,10 +139,18 @@ describe('object', function () {
       assert.strictEqual(o2.foo, 'bar')
       assert.strictEqual(hasOwnProperty(o2, 'foo'), false)
 
       delete Object.prototype.foo
     })
+
+    it('should not pollute Object.__proto__', function () {
+      const obj = {}
+      assert.strictEqual(obj.polluted, undefined)
+
+      deepExtend(obj, JSON.parse('{"__proto__":{"polluted":"yes"}}'))
+      assert.strictEqual(obj.polluted, undefined)
+    })
   })
 
   describe('deepEqual', function () {
     it('should deep compare two objects', function () {
       assert.strictEqual(deepStrictEqual({}, {}), true)

Vulnerable code:     if (hasOwnProperty(b, prop)) {
Fixed code: # not yet published, version 7.5.1

- Fix object pollution vulnerability in `math.config`. Thanks Snyk.


    // We check against prop not being in Object.prototype or Function.prototype
    // to prevent polluting for example Object.__proto__.
    if (hasOwnProperty(b, prop) && !(prop in Object.prototype) && !(prop in Function.prototype)) {

  it('should not allow polluting the Object prototype via config', () => {
    const obj = {}
    assert.strictEqual(obj.polluted, undefined)

    // change the configuration
    const newConfig = JSON.parse('{"__proto__":{"polluted":"yes"}}')
    math.config(newConfig)
    assert.strictEqual(obj.polluted, undefined)
  })

  it('should not allow polluting the Object prototype via config via the expression parser', () => {
    const obj = {}
    assert.strictEqual(obj.polluted, undefined)

    // change the configuration
    math.evaluate('config({"__proto__":{"polluted":"yes"}})')
    assert.strictEqual(obj.polluted, undefined)
  })

  it('should not allow polluting the Object prototype by creating an object in the expression parser', () => {
    const obj = {}
    assert.strictEqual(obj.polluted, undefined)

    // change the configuration
    math.evaluate('a = {"__proto__":{"polluted":"yes"}}')
    assert.strictEqual(obj.polluted, undefined)
  })

    it('should not pollute Object.__proto__', function () {
      const obj = {}
      assert.strictEqual(obj.polluted, undefined)

      deepExtend(obj, JSON.parse('{"__proto__":{"polluted":"yes"}}'))
      assert.strictEqual(obj.polluted, undefined)
    })