Feb 21, 2026·6 min read·35 visits
A critical RCE in math.js < 3.17.0 allows attackers to execute arbitrary system code. The vulnerability exploits a disparity between how the security validator reads strings vs. how the JS engine executes them. By masking forbidden property names (like 'constructor') with Unicode escapes (e.g., 'co\u006Estructor'), attackers can bypass the sandbox and pop a shell.
Math.js, the popular extensive math library for JavaScript and Node.js, suffered from a critical sandbox escape vulnerability. By utilizing Unicode escape sequences in object property keys, attackers could bypass the library's internal security blacklist. This allowed access to the `constructor` property, enabling the execution of arbitrary JavaScript code via the `Function` constructor. Essentially, the security guard was checking ID cards for 'Admin', but let 'Adm\u0069n' walk right through the front door.
We tend to trust math. It's absolute, deterministic, and safe. When a developer imports a library like math.js to handle complex expressions or user-defined formulas, they usually assume the worst-case scenario is a division by zero or maybe a resource exhaustion loop. They rarely expect that calculating 2 + 2 could result in a reverse shell opening up on their production server.
math.js is a beast of a library. It handles matrices, big numbers, and crucially, it comes with an expression parser that allows users to define variables and functions. To support this flexibility, the library implements a custom scope—a sandbox. The goal is simple: allow the user to do math, but don't let them touch the underlying JavaScript engine's sensitive internals, specifically the Object prototype chain.
But here is the thing about JavaScript sandboxes: they are like trying to hold water in your hands. Eventually, it leaks. In CVE-2017-1001003, the leak wasn't a complex memory corruption or a race condition. It was a linguistic misunderstanding between the security guard (the validator) and the executioner (the runtime). The validator only spoke ASCII, but the runtime was fluent in Unicode.
The core of the vulnerability lies in the isSafeProperty function. This function serves as the bouncer for the math.js expression evaluator. Its job is to check every property access or object definition against a blacklist of forbidden words. Naturally, this list includes the usual suspects: constructor, __proto__, prototype, and __defineGetter__. If you try to run math.eval('a.constructor'), the bouncer sees the word "constructor", panics, and throws an error. System secure. Good job, everyone.
However, the flaw is a classic example of Canonicalization Issues (CWE-20). The validator checked the raw string provided in the expression. If I type co\u006Estructor, the validator sees a string containing a backslash, a 'u', and some numbers. It compares co\u006Estructor against constructor. Strings are not equal. The bouncer waves it through.
But later down the pipeline, the JavaScript engine—which is compliant with the ECMAScript standard—looks at that same string. It sees the escape sequence \u006E and politely translates it into the character n. Suddenly, co\u006Estructor becomes constructor. The attacker has now accessed the forbidden property, effectively turning the "safe" math expression evaluator into a remote code execution engine.
Let's look at the code responsible for this mess. The vulnerability resided in lib/expression/node/ObjectNode.js, specifically where object properties were being processed.
The Vulnerable Code:
// The naive check
if (!isSafeProperty(node.properties, key)) {
throw new Error('No access to property "' + key + '"');
}Here, key is the raw identifier from the parsed expression tree. It hasn't been normalized. It is exactly what the user typed. If the user types Unicode escapes, key contains them literally.
The Fix (Commit a60f3c8):
The maintainer, Jos de Jong, realized that to validate input correctly, you must view the input exactly as the interpreter will view it. The fix forces the key to be "stringified" and then parsed back, which forces the resolution of Unicode escapes before the security check happens.
// The robust check
var stringifiedKey = stringify(key);
var parsedKey = JSON.parse(stringifiedKey);
// Now we check the resolved version of the key
if (!isSafeProperty(node.properties, parsedKey)) {
throw new Error('No access to property "' + parsedKey + '"');
}By round-tripping the key through JSON.parse(stringify(...)), co\u006Estructor is transformed into constructor before it hits isSafeProperty. The bouncer now recognizes the disguise.
Exploiting this is trivially elegant. We don't need buffer overflows or heap spraying. We just need to ask JavaScript to give us the Function constructor.
In JavaScript, if you have an object obj, obj.constructor returns the function that created it (usually Object). obj.constructor.constructor returns the Function constructor. The Function constructor is effectively eval()—it takes a string of code and creates a function that executes it.
The Attack Chain:
constructor, but spell it with Unicode: {"co\u006Estructor": 1}.Object constructor..constructor of the Object constructor to get the global Function constructor.The Payload:
// This looks like a math expression, but it executes 'kill'
math.eval(`
a = {"co\\u006Estructor": 1};
a.constructor.constructor("return process.kill(process.pid)")()
`);In a real-world scenario, instead of killing the process, an attacker would use child_process (if available in the context) or standard filesystem APIs to exfiltrate environment variables (AWS keys, DB creds) or establish a reverse shell.
This vulnerability scored a CVSS 9.8 for a reason. It is unauthenticated, requires no user interaction (other than the system processing the input), and results in total compromise of the application's confidentiality, integrity, and availability.
Consider where math.js is used. It is often embedded in SaaS platforms to allow users to define custom pricing formulas, financial models, or scientific calculations. These features are by definition "remote code execution as a feature," restricted only by the sandbox. CVE-2017-1001003 dissolves that sandbox entirely.
If your backend runs this code, the attacker runs as the user of your Node.js process. They can read your .env files, dump your database, or use your server as part of a botnet. The "math" library becomes the foothold for a full infrastructure compromise.
The remediation is straightforward: Update math.js to version 3.17.0 or later immediately.
If you cannot update for some reason (legacy dependency hell), you have to implement a sanitizer before the input reaches math.eval. A rough mitigation would be to reject any input containing the \u sequence, although this might break legitimate mathematical notation or string usage depending on your use case.
> [!NOTE] > Developer Takeaway: When validating input, always validate the canonical form of the data. If your system transforms data (URL decoding, Unicode normalization, XML parsing) after your security check, you are vulnerable. Always normalize first, then validate.
CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
math.js josdejong | < 3.17.0 | 3.17.0 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-20 / CWE-88 |
| CVSS v3.0 | 9.8 (Critical) |
| Attack Vector | Network (Remote) |
| Exploit Status | PoC Available |
| EPSS Score | 0.49% |
| Impact | Arbitrary Code Execution |
A vulnerability exists in the i18n middleware of nginx-ignition, enabling CPU amplification attacks. By transmitting a crafted Accept-Language header containing malformed tags separated by underscores, an unauthenticated remote attacker can bypass the length-guard threshold of the underlying Go parsing library. Normalization of underscores to hyphens occurs after the initial validation checks, forcing the parser into expensive quadratic-time loops that consume 100% of available CPU resources. This leads to a complete denial of service for the administrative API and potentially degrades the availability of the hosting system. This vulnerability has been resolved in version 2.40.1.
Nginx Ignition prior to version 2.41.1 contains a Time-of-Check to Time-of-Use (TOCTOU) race condition in its unauthenticated onboarding API endpoint. This flaw allows remote, unauthenticated attackers to register an administrative account by sending concurrent HTTP requests during the initial system configuration phase, bypassing the check meant to restrict onboarding to a single initial administrator.
A logic error in Hatchet's OAuth state validation mechanism allows unauthenticated remote attackers to bypass state parameter verification. By submitting an empty state parameter, attackers can exploit an equality collision with cleared session keys, facilitating Login Cross-Site Request Forgery (Login CSRF) or Session Fixation.
A high-severity access control vulnerability in ToolHive CLI before v0.30.1 and ToolHive Studio before v0.38.0 allows local containerized MCP servers to bypass network isolation. This enables malicious workloads to establish TCP/IP connections to administrative and control plane endpoints exposed on the host loopback interface.
AnyCable is a real-time communication server. Prior to version 1.6.15, its Pusher-compatible REST API suffered from an authentication bypass vulnerability because it failed to verify that the request body matched the signature-validated body_md5 parameter. This allows attackers to perform replay attacks with modified body contents.
A denial-of-service vulnerability exists in AnyIO prior to version 4.14.2. Standard error streams of process-pool workers are connected to an operating system pipe that is never drained by the parent process. This allows a worker to fill the pipe buffer and deadlock indefinitely.