CVSS: 10.0
Target Service
SandboxJS
- JavaScript sandbox library for running JavaScript code in an isolated environment.
- A library aimed at providing an untrusted code execution environment by restricting access to host global objects and dangerous built-in objects.
- Internally maintains sandbox boundaries based on allowed prototypes and property sets, wrapper objects, taint tracking, execution limits, etc.
- Can be used for dynamic code execution, plug-in processing, and user script isolation in Node.js environments, etc.
Affected Versions
- Affected versions: All versions prior to SandboxJS 0.8.34
- Patch version: SandboxJS 0.8.34
Vulnerability Overview
Vulnerability Types
- Sandbox Escape
- RCE
- Unauthenticated arbitrary code execution possible structure
- Bypass taint tracking
- Dangerous host functions are mistaken for safe values during array and object reconstruction
- Exposure of Function constructor
- A reference to the host Function constructor can be obtained.
- Promise chain-based execution
- Promise.prototype.finally and the reconfigured then property can be used to execute code in a host-global context.
Root Cause
- There was a structural flaw in SandboxJS’s taint tracking mechanism.
- When an attacker moves a dangerous host function reference to a new array using built-in methods such as Object.values, the sandbox engine fails to recursively propagate the taint to the elements inside the array.
- As a result, after the host function constructor goes through the array access and Object.fromEntries reconstruction process, it is treated like a normal value rather than a dangerous object.
Object.values,Object.fromEntries, and similar methods are allowed by theSAFE_PROTOTYPESconfiguration inSandboxExec.ts, letting an attacker use this data-transformation path as if it were normal functionality.- When
executor.tshandlesLispType.Call, it invokes the native method directly and performs only the shallow cleanupgetGlobalProp(ret, context) || ret. If the return value is an array or object, its nested elements and properties are not inspected. - Later,
LispType.CreateArrayunwraps each wrapper withvalueOrProp(item, context)but does not revalidate whether the resulting value is a dangerous host reference. - As a result, the leaked host function reference is processed into a “general function reference” inside the sandbox, making it possible to finally escape the sandbox.
Exploitation Mechanism
Attack Flow
- Attacker → Call Object.values(this) from sandbox internal code
- Obtains an array containing dangerous constructor references such as Function among the values mapped to the host global object.
- Sandbox internal processing
- LispType.Call in executor.ts directly calls the original Object.values
- Perform a shallow check only on the entire returned array
- Dangerous function references inside arrays are returned uncleaned.
- Data type conversion
- The attacker creates an object in the form of { then: Function } using array expansion and Object.fromEntries.
- The internal values are not recursively sanitized during this process either.
- Promise chain abuse
- Combining an object with a native Promise.prototype.finally and a reconstructed then property
- When finally is executed, the host engine directly reads the object’s then property.
- Calling the host Function constructor
- Since then points to the actual host Function constructor, the passed string is executed in the global host context.
- Sandbox escape and arbitrary code execution
If an attacker executes Object.values(...) in sandbox code, the call is permitted by the SAFE_PROTOTYPES setting. executor.ts then invokes the host environment’s native Object.values implementation and processes the result with getGlobalProp(ret, context) || ret. This check examines only the returned array itself and does not sanitize dangerous host references such as Function within the array. The sandbox therefore receives a tainted array containing the host Function constructor.
When the attacker then performs array spreading or a similar operation, LispType.CreateArray unwraps each element through valueOrProp(item, context). Even at this stage, the resulting value is not checked again to determine whether it is a dangerous host-global object. Once the host Function reference leaks, it is no longer treated as dangerous and propagates through the sandbox as an ordinary value.
The actual exploit uses Object.fromEntries([['then', ...Object.values(this).slice(1)]]) to reconstruct array elements as object properties. This makes the host Function constructor appear to be an ordinary object with a then property. The attacker then combines it with an object backed by the native Promise.prototype.finally and calls finally, causing the host engine to read and invoke then outside the sandbox. Because then points to the real host Function constructor, Function('malicious code') executes in the host’s global scope and completely escapes the sandbox.
Patch Details
SandboxJS 0.8.34
Incorporated fixes to reduce the potential for host reference leaks and sandbox state contamination.
- Vulnerability was fixed in version 0.8.34
- Prevents issues where host function references remain as safe values during array/object reconfiguration.
- Includes fixes to reduce global/external dependency state related to execution constraints.
- Specifically ticks || Fallback logic such as currentTicks.current has been removed and changed to use the fixed context.ctx.ticks.
- The existing architecture could rely on ticks passed in externally or currentTicks.current at the global level, leaving the sandbox execution budget susceptible to external state.
- After modification, the execution limit is fixed to the context at the time of sandbox creation, preventing it from being changed by external factors during the function creation stage.