CVE-2017-1001002: Improper Control of Generation of Code ('Code Injection') (CWE-94)
value: math.js before 3.17.0 had an arbitrary code execution in the JavaScript engine. Creating a typed function with JavaScript code in the name could result arbitrary execution.
CWE description: The product constructs all or part of a code segment using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the syntax or behavior of the intended code segment.
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/parse.js, package.json, test/expression/parse.test.js, test/expression/security.test.js
Commit date: 2017-11-18 19:39:30 +0100
Commit message: Fixed a security issue in `typed-function` allowing arbitrary code execution

Diff: diff --git a/HISTORY.md b/HISTORY.md
index 3c27fa90..6c1c4353 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -2,10 +2,13 @@
 
 
 ## not yet released, version 3.17.0
 
 - 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.
 
 
 ## 2017-10-18, version 3.16.5
 
 - Fixed #954: Functions `add` and `multiply` not working when
diff --git a/lib/expression/parse.js b/lib/expression/parse.js
index aef74f96..f956429a 100644
--- a/lib/expression/parse.js
+++ b/lib/expression/parse.js
@@ -590,11 +590,11 @@ function factory (type, config, load, typed) {
         // parse a matrix subset assignment like 'A[1,2] = 4'
         getTokenSkipNewline();
         value = parseAssignment();
         return new AssignmentNode(node.object, node.index, value);
       }
-      else if (type.isFunctionNode(node)) {
+      else if (type.isFunctionNode(node) && type.isSymbolNode(node.fn)) {
         // parse function assignment like 'f(x) = x^2'
         valid = true;
         args = [];
 
         name = node.name;
diff --git a/package.json b/package.json
index 6f438b88..c6a849a8 100644
--- a/package.json
+++ b/package.json
@@ -91,11 +91,11 @@
     "decimal.js": "7.2.3",
     "fraction.js": "4.0.2",
     "javascript-natural-sort": "0.7.1",
     "seed-random": "2.2.0",
     "tiny-emitter": "2.0.0",
-    "typed-function": "0.10.5"
+    "typed-function": "0.10.6"
   },
   "devDependencies": {
     "benchmark": "2.1.4",
     "expr-eval": "1.0.1",
     "glob": "7.1.2",
diff --git a/test/expression/parse.test.js b/test/expression/parse.test.js
index 8a2a6545..09a6f686 100644
--- a/test/expression/parse.test.js
+++ b/test/expression/parse.test.js
@@ -810,10 +810,17 @@ describe('parse', function() {
       var obj = parseAndEval('{f: f(x)=x^2}');
       assert.deepEqual(Object.keys(obj), ['f']);
       assert.equal(obj.f(2), 4);
     });
 
+    it('should not parse a function assignment in an accessor node', function () {
+      assert.throws(function () {
+        var scope = {}
+        var obj = parseAndEval('a["b"](x)=x^2', scope);
+      }, /SyntaxError: Invalid left hand side of assignment operator =/)
+    });
+
     it('should parse an object containing a variable assignment', function () {
       var scope = {};
       assert.deepEqual(parseAndEval('{f: a=42}', scope), {f: 42});
       assert.strictEqual(scope.a, 42);
     });
diff --git a/test/expression/security.test.js b/test/expression/security.test.js
index 5583a1b6..5078cce5 100644
--- a/test/expression/security.test.js
+++ b/test/expression/security.test.js
@@ -286,10 +286,28 @@ describe('security', function () {
     assert.throws(function () {
       math.eval("x=parse(\"a\",{nodes:{a:Chain}});Chain.bind(x,{})();evilMath=x.create().done();evilMath.import({\"_compile\":f(a,b,c)=\"eval\",\"isNode\":f()=true}); parse(\"(1)\").map(g(a,b,c)=evilMath.chain()).compile().eval()(\"console.log(\'hacked...\')\")");
     }, /Undefined symbol Chain/);
   })
 
+  it ('should not allow passing a function name containg bad contents', function () {
+    // underlying issues where:
+    // the input '[]["fn"]()=0'   
+    // - defines a function in the root scope, but this shouldn't be allowed syntax
+    // - there is a typed function created which unsecurely evaluates JS code with the function name in it 
+    //   -> when the function name contains JS code it can be executed, example:
+    //
+    //         var fn = typed("(){}+console.log(`hacked...`);function a", { "": function () { } })
+
+    assert.throws(function () {
+      math.eval('[]["(){}+console.log(`hacked...`);function a"]()=0')
+    }, /SyntaxError: Invalid left hand side of assignment operator =/);
+
+    assert.throws(function () {
+      math.eval('{}["(){}+console.log(`hacked...`);function a"]()=0')
+    }, /SyntaxError: Invalid left hand side of assignment operator =/);
+  })
+
   it ('should allow calling functions on math', function () {
     assert.equal(math.eval('sqrt(4)'), 2);
   })
 
   it ('should allow invoking methods on complex numbers', function () {

Vulnerable code:       else if (type.isFunctionNode(node)) {
    "typed-function": "0.10.5"
Fixed code: - 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.
      else if (type.isFunctionNode(node) && type.isSymbolNode(node.fn)) {
    "typed-function": "0.10.6"
    it('should not parse a function assignment in an accessor node', function () {
      assert.throws(function () {
        var scope = {}
        var obj = parseAndEval('a["b"](x)=x^2', scope);
      }, /SyntaxError: Invalid left hand side of assignment operator =/)
    });

  it ('should not allow passing a function name containg bad contents', function () {
    // underlying issues where:
    // the input '[]["fn"]()=0'   
    // - defines a function in the root scope, but this shouldn't be allowed syntax
    // - there is a typed function created which unsecurely evaluates JS code with the function name in it 
    //   -> when the function name contains JS code it can be executed, example:
    //
    //         var fn = typed("(){}+console.log(`hacked...`);function a", { "": function () { } })

    assert.throws(function () {
      math.eval('[]["(){}+console.log(`hacked...`);function a"]()=0')
    }, /SyntaxError: Invalid left hand side of assignment operator =/);

    assert.throws(function () {
      math.eval('{}["(){}+console.log(`hacked...`);function a"]()=0')
    }, /SyntaxError: Invalid left hand side of assignment operator =/);
  })