CVE-2017-1001003

Math.js: When 1 + 1 Equals Root Shell (CVE-2017-1001003)

Amit Schendel
Amit Schendel
Senior Security Researcher

Jan 12, 2026·6 min read

Executive Summary (TL;DR)

The math.js library attempted to block access to dangerous properties like `constructor` by comparing property names against a blacklist. However, it failed to normalize Unicode escape sequences before this check. An attacker could pass `co\u006Estructor`, which evades the textual blacklist but is resolved by the JavaScript engine to `constructor`. This grants access to the `Function` constructor, enabling arbitrary code execution on the host server.

A critical sandbox escape in the popular math.js library allows attackers to bypass property restrictions using Unicode escape sequences, leading to Remote Code Execution via the constructor chain.

The Hook: It's Just Math, Right?

JavaScript sandboxes are like screen doors on a submarine. They look solid, they feel safe, but under enough pressure (or just the wrong kind of poke), they fold instantly. Developers love math.js because it provides a powerful expression parser that feels safer than the terrifying eval(). You pass it a string like 2 * x + 5, give it a scope, and it returns a number. Clean. Simple.

But here is the problem: to be useful, math.js needs to handle objects. And in JavaScript, objects are dynamic minefields. If you give a user the ability to define objects, you are one step away from giving them the keys to the kingdom.

To prevent this, math.js implemented what we in the industry call "The Bouncer approach." It had a blacklist of bad words you couldn't use as object keys, specifically constructor, __proto__, and prototype. If you tried math.eval('x.constructor'), it would throw an error. Secure, right? Well, only if you assume the Bouncer speaks every dialect of the language.

The Flaw: Lost in Translation

The vulnerability (CVE-2017-1001003) is a classic case of normalization inconsistency. It falls under the umbrella of "Check, Then Act," but specifically, checking the raw representation while acting on the resolved representation.

When math.js parses an expression like {'key': 'value'}, it has to validate the key. In the vulnerable versions (pre-3.17.0), the code looked at the raw string literal provided by the user. If you wrote {'constructor': 1}, the validator saw the string "constructor", panicked, and blocked it.

However, JavaScript allows Unicode escape sequences in identifiers and strings. The sequence \u006E represents the letter n. If an attacker wrote {'co\u006Estructor': 1}, the validator saw the literal string "co\\u006Estructor". It compared that string to "constructor". Obviously, they aren't the same string. The validator shrugged and let it through.

[!NOTE] The validator was checking the syntax (what was typed), but the engine executes the semantics (what it means).

Once the validation passed, the code was compiled and handed off to the JavaScript engine. The engine, being helpful and compliant, saw co\u006Estructor, decoded the Unicode escape, and accessed the actual constructor property. The Bouncer let in a guy named "V\u006Fldemort" because the list only said "Voldemort".

The Code: The Smoking Gun

Let's look at the crime scene in lib/expression/node/ObjectNode.js. The code iterates over the properties of an object defined in the expression and checks them against isSafeProperty.

Here is the vulnerable logic:

// PRE-PATCH VULNERABLE CODE
for (var key in node.properties) {
  // 'key' here is the raw string from the parser, e.g., "co\\u006Estructor"
  if (!isSafeProperty(node.properties, key)) {
    throw new Error('No access to property "' + key + '"');
  }
  // ... compilation proceeds ...
}

The isSafeProperty function was doing a strict string comparison. It didn't know that \u006E is just a fancy n.

The fix, introduced in version 3.17.0 (commit a60f3c8), forces the key to reveal its true form before the ID check. It effectively says, "I don't care how you spell it, tell me what you are."

// PATCHED CODE
// stringify/parse resolves the unicode characters
var stringifiedKey = stringify(key);
var parsedKey = JSON.parse(stringifiedKey); // "co\\u006Estructor" becomes "constructor"
 
if (!isSafeProperty(node.properties, parsedKey)) {
  throw new Error('No access to property "' + parsedKey + '"');
}

By round-tripping the key through JSON serialization, any escape sequences are resolved. "co\u006Estructor" becomes "constructor", and the isSafeProperty check finally catches it.

The Exploit: Climbing the Prototype Chain

So we can access constructor. Why does that matter? In JavaScript, constructor is the gateway to the metaphysical underpinnings of the runtime. If you have an object, obj.constructor gives you Object. obj.constructor.constructor gives you Function.

The Function constructor is essentially eval()'s more sophisticated cousin. It takes a string of code and returns a callable function. If we can reach it, we win.

Here is the attack chain visualised:

And here is the payload logic. We define an object where the key is the Unicode-escaped constructor. The value of that key is another object (because constructor returns a function, and we need to chain deeper).

Eventually, we construct a payload that looks like this to math.eval():

math.eval(`
  {
    "co\\u006Estructor": {
      "co\\u006Estructor": "return process.mainModule.require('child_process').execSync('cat /etc/passwd').toString()"
    }
  }()
`);

Wait, why the nested constructor?

  1. The outer object's constructor property is set.
  2. When we access it, we are actually accessing the Object constructor.
  3. We walk up to Object.constructor (which is Function).
  4. We pass our malicious string to Function.
  5. The final () invokes the created function.

Boom. You are now the www-data user.

The Impact: Calculator to Command Line

This isn't just a Cross-Site Scripting (XSS) bug where you annoy a user with an alert box. Because math.js is often used in Node.js environments to handle server-side calculations (pricing engines, scientific data processing, smart contracts), this is a full Remote Code Execution (RCE).

If you are running a service that accepts math formulas from users—perhaps a finance app calculating loan interest or a student portal plotting graphs—and you are using a vulnerable version of math.js, an attacker owns your server.

They can:

  • Read environment variables (API keys, AWS credentials).
  • Exfiltrate source code.
  • Install persistence or crypto miners.
  • Pivot to the internal network.

CVSS 9.8 is not a suggestion; it's a scream. The barrier to entry is zero authentication and a single HTTP request.

The Fix: Normalization is Key

The remediation is straightforward: Update math.js to version 3.17.0 or higher. The maintainers fixed this back in late 2017, so if you are still vulnerable, you are practicing digital archaeology.

If you are a developer writing your own parsers or validators, the lesson is broader: Canonicalize before you validate.

Never check the raw input if the system processing that input interprets it differently than your checker does. This applies to:

  • Unicode escapes in JSON/JS.
  • URL encoding in WAFs (%2e%2e/ vs ../).
  • Case sensitivity in file systems.

If you cannot upgrade immediately (why?), you would need to manually sanitize input strings to reject \ characters before passing them to math.eval, but honestly, just upgrade. Don't try to outsmart the parser; you will lose.

Fix Analysis (1)

Technical Appendix

CVSS Score
9.8/ 10
CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
EPSS Probability
0.49%
Top 35% most exploited

Affected Systems

Node.js applications using math.js < 3.17.0Web applications using math.js client-side (impacts user session via XSS/Context manipulation)

Affected Versions Detail

Product
Affected Versions
Fixed Version
math.js
Jos de Jong
< 3.17.03.17.0
AttributeDetail
CWE IDCWE-88
CVSS v3.09.8 (Critical)
VectorCVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Attack VectorNetwork (Input Injection)
EPSS Score0.48%
Exploit StatusPoC Available
CWE-88
Argument Injection or Modification

Improper Neutralization of Argument Delimiters or Special Characters

Vulnerability Timeline

Vulnerability identified and fixed in version 3.17.0
2017-11-18
CVE-2017-1001003 Published
2017-11-27

Subscribe to updates

Get the latest CVE analysis reports delivered to your inbox.