CVE-2017-1001003: Improper Input Validation (CWE-20)
value: math.js before 3.17.0 had an issue where private properties such as a constructor could be replaced by using unicode characters when creating an object.
CWE description: The product receives input or data, but it does
        not validate or incorrectly validates that the input has the
        properties that are required to process the data safely and
        correctly.
CVSS v3 base score: 9.8
Published: 2017-11-27T14:29Z
Repository: https://github.com/josdejong/mathjs
Language: JavaScript
Files changed: HISTORY.md, lib/expression/node/ObjectNode.js, test/expression/security.test.js, test/utils/customs.test.js
Commit date: 2017-11-18 20:11:48 +0100
Commit message: Fixed a security issue where forbidden properties like constructor could be replaced by using unicode characters when creating an object

Diff: diff --git a/HISTORY.md b/HISTORY.md
index 6c1c43534..78f708b49 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -5,10 +5,13 @@
 
 - Improved `simplify` for nested exponentiations. Thanks @IvanVergiliev.
 - Fixed a security issue in `typed-function` allowing arbitrary code execution
   in the JavaScript engine by creating a typed function with JavaScript code
   in the name. Thanks Masato Kinugawa.
+- Fixed a security issue where forbidden properties like constructor could be
+  replaced by using unicode characters when creating an object. No known exploit,
+  but could possibly allow arbitrary code execution. Thanks Masato Kinugawa.
 
 
 ## 2017-10-18, version 3.16.5
 
 - Fixed #954: Functions `add` and `multiply` not working when
diff --git a/lib/expression/node/ObjectNode.js b/lib/expression/node/ObjectNode.js
index 22681adae..ffb6125e6 100644
--- a/lib/expression/node/ObjectNode.js
+++ b/lib/expression/node/ObjectNode.js
@@ -58,15 +58,19 @@ function factory (type, config, load, typed) {
     }
 
     var entries = [];
     for (var key in node.properties) {
       if (hasOwnProperty(node.properties, key)) {
-        if (!isSafeProperty(node.properties, key)) {
-          throw new Error('No access to property "' + key + '"');
+        // we stringify/parse the key here to resolve unicode characters,
+        // so you cannot create a key like {"co\\u006Estructor": null} 
+        var stringifiedKey = stringify(key)
+        var parsedKey = JSON.parse(stringifiedKey)
+        if (!isSafeProperty(node.properties, parsedKey)) {
+          throw new Error('No access to property "' + parsedKey + '"');
         }
 
-        entries.push(stringify(key) + ': ' + compile(node.properties[key], defs, args));
+        entries.push(stringifiedKey + ': ' + compile(node.properties[key], defs, args));
       }
     }
     return '{' + entries.join(', ') + '}';
   }
 
diff --git a/test/expression/security.test.js b/test/expression/security.test.js
index 5078cce50..4e327c81d 100644
--- a/test/expression/security.test.js
+++ b/test/expression/security.test.js
@@ -81,10 +81,27 @@ describe('security', function () {
           '{}.constructor.assign(cos.constructor, {bind: null})\n' +
           'cos.constructor.binding()("console.log(\'hacked...\')")()');
     }, /Error: No access to property "bind/);
   })
 
+  it ('should not allow disguising forbidden properties with unicode characters', function () {
+    var scope = {
+      a: {}
+    };
+
+    assert.throws(function () { math.eval('a.co\u006Estructor', scope); }, /Error: No access to property "constructor"/);
+    assert.throws(function () { math.eval('a["co\\u006Estructor"]', scope); }, /Error: No access to property "constructor"/);
+    assert.throws(function () { math.eval('a.constructor', scope); }, /Error: No access to property "constructor"/);
+    assert.throws(function () { math.eval('a.constructor = 2', scope); }, /Error: No access to property "constructor"/);
+    assert.throws(function () { math.eval('a["constructor"] = 2', scope); }, /Error: No access to property "constructor"/);
+    assert.throws(function () { math.eval('a["co\\u006Estructor"] = 2', scope); }, /Error: No access to property "constructor"/);
+    assert.throws(function () { math.eval('a = {"constructor": 2}', scope); }, /Error: No access to property "constructor"/);
+    assert.throws(function () { math.eval('a = {constructor: 2}', scope); }, /Error: No access to property "constructor"/);
+    assert.throws(function () { math.eval('a = {"co\\u006Estructor": 2}', scope); }, /Error: No access to property "constructor"/);
+    assert.throws(function () { math.eval('a = {co\u006Estructor: 2}', scope); }, /Error: No access to property "constructor"/);
+  })
+
   it ('should not allow calling Function via imported, overridden function', function () {
     assert.throws(function () {
       var math2 = math.create();
       math2.eval('import({matrix:cos.constructor},{override:1});x=["console.log(\'hacked...\')"];x()');
     }, /Error: Undefined symbol import/);
diff --git a/test/utils/customs.test.js b/test/utils/customs.test.js
index 5f362ad82..1f662f9c2 100644
--- a/test/utils/customs.test.js
+++ b/test/utils/customs.test.js
@@ -83,10 +83,13 @@ describe ('customs', function () {
       assert.equal(customs.isSafeMethod(matrix, '__lookupGetter__'), false);
       assert.equal(customs.isSafeMethod(matrix, '__lookupSetter__'), false);
 
       // non existing method
       assert.equal(customs.isSafeMethod(matrix, 'nonExistingMethod'), false);
+
+      // method with unicode chars
+      assert.equal(customs.isSafeMethod(matrix, 'co\u006Estructor'), false);
     });
 
   });
 
   describe ('isSafeProperty', function () {
@@ -111,10 +114,12 @@ describe ('customs', function () {
       assert.equal(customs.isSafeProperty(object, 'caller'), false);
 
       // non existing property
       assert.equal(customs.isSafeProperty(object, 'bar'), true);
 
+      // property with unicode chars
+      assert.equal(customs.isSafeProperty(object, 'co\u006Estructor'), false);
     });
 
     it ('should test inherited properties on plain objects ', function () {
       var object1 = {};
       var object2 = Object.create(object1);

Vulnerable code:         if (!isSafeProperty(node.properties, key)) {
          throw new Error('No access to property "' + key + '"');
        entries.push(stringify(key) + ': ' + compile(node.properties[key], defs, args));
Fixed code: - Fixed a security issue where forbidden properties like constructor could be
  replaced by using unicode characters when creating an object. No known exploit,
  but could possibly allow arbitrary code execution. Thanks Masato Kinugawa.
        // we stringify/parse the key here to resolve unicode characters,
        // so you cannot create a key like {"co\\u006Estructor": null} 
        var stringifiedKey = stringify(key)
        var parsedKey = JSON.parse(stringifiedKey)
        if (!isSafeProperty(node.properties, parsedKey)) {
          throw new Error('No access to property "' + parsedKey + '"');
        entries.push(stringifiedKey + ': ' + compile(node.properties[key], defs, args));
  it ('should not allow disguising forbidden properties with unicode characters', function () {
    var scope = {
      a: {}
    };

    assert.throws(function () { math.eval('a.co\u006Estructor', scope); }, /Error: No access to property "constructor"/);
    assert.throws(function () { math.eval('a["co\\u006Estructor"]', scope); }, /Error: No access to property "constructor"/);
    assert.throws(function () { math.eval('a.constructor', scope); }, /Error: No access to property "constructor"/);
    assert.throws(function () { math.eval('a.constructor = 2', scope); }, /Error: No access to property "constructor"/);
    assert.throws(function () { math.eval('a["constructor"] = 2', scope); }, /Error: No access to property "constructor"/);
    assert.throws(function () { math.eval('a["co\\u006Estructor"] = 2', scope); }, /Error: No access to property "constructor"/);
    assert.throws(function () { math.eval('a = {"constructor": 2}', scope); }, /Error: No access to property "constructor"/);
    assert.throws(function () { math.eval('a = {constructor: 2}', scope); }, /Error: No access to property "constructor"/);
    assert.throws(function () { math.eval('a = {"co\\u006Estructor": 2}', scope); }, /Error: No access to property "constructor"/);
    assert.throws(function () { math.eval('a = {co\u006Estructor: 2}', scope); }, /Error: No access to property "constructor"/);
  })


      // method with unicode chars
      assert.equal(customs.isSafeMethod(matrix, 'co\u006Estructor'), false);
      // property with unicode chars
      assert.equal(customs.isSafeProperty(object, 'co\u006Estructor'), false);