CVE-2023-30547 - Breaking Out of vm2 Sandbox – Attack and Exploit Details
Published: 2024-06-27
*By: DevSec Exclusive*
Sandboxing is a key part of many secure JavaScript applications that need to execute untrusted code—think online REPLs, plugin systems, or even processing data from third parties. The vm2 package has been a really popular library for this in the Node.js world. Unfortunately, CVE-2023-30547 revealed a critical flaw that made escaping this sandbox possible, putting host systems at risk.
In this post, we'll break down the vulnerability, share exploit details, and give advice on staying safe.
What is vm2?
vm2 is a JavaScript sandbox for Node.js that provides a custom virtual machine, letting users run untrusted code with only some built-in Node.js modules available. For example, code in a sandbox shouldn’t be able to access your server’s filesystem or environment variables.
Example usage
const { NodeVM } = require('vm2');
const vm = new NodeVM({
require: {
external: true,
builtin: ['fs', 'path']
}
});
const result = vm.run('module.exports = 2 + 2');
console.log(result); // 4
If properly configured, even malicious code should not break out or access the real system.
What was the vulnerability?
CVE-2023-30547 is an issue in how vm2 handled *exception sanitization* up to version 3.9.16. Specifically, unsanitized exceptions could be raised using the internal handleException() method, and those exceptions could break out of the sandbox protection boundary.
In simple terms: An attacker could trigger certain errors inside the sandbox in a special way so that the error "jumps" outside the sandbox’s control mechanisms—letting attacker code run with the same privileges as your host Node.js process.
Why does this matter?
Most developers use vm2 because they need to run code that can't be trusted. If the sandbox can be bypassed, malicious users could access secrets, change files, or even take over the server.
Detailed Exploit Scenario
Let’s walk through a simplified exploit flow (in pseudo-code), and then show how it could look in practice.
Install a vulnerable version (≤ 3.9.16) of vm2
npm install vm2@3.9.16
Here’s what a basic sandbox setup looks like
const { NodeVM } = require('vm2');
const vm = new NodeVM({
sandbox: {} // Basic, no extra protections
});
try {
vm.run(`
// Malicious code would go here
throw Error("breakout!");
`);
} catch (e) {
console.log("Caught exception:", e);
}
The issue arises because the internal method that catches and 'sanitizes' exceptions in vm2 was not properly handling all exception types. With clever crafting, it was possible to introduce an error that vm2 failed to sanitize, allowing the error’s context to leak into the host. This could give access to host-side variables, functions, or even modules.
Here’s a redacted proof of concept *for learning purposes only*
const {NodeVM} = require('vm2');
const vm = new NodeVM({
sandbox: {}
});
try {
vm.run(`
const error = new Error("unsanitized");
error.constructor.constructor('process.exit()')();
`);
} catch (e) {
console.log("error:", e);
}
What happens here:
error.constructor.constructor is actually Function, providing a way to execute arbitrary code.
- With the bug, the error construction and handling chain can be leveraged to run code in the host context.
Note:
A real-world attacker could use this method to run anything, not just shut down your app.
There are *NO* Workarounds
As per the official advisory:
> There are no known workarounds for this vulnerability. Users are advised to upgrade.
If your code or any dependencies use vm2, upgrade to version 3.9.17 or later immediately.
References
- GHSA-4rgh-56mj-rp9p Security Advisory
- NPM Advisory for vm2
- vm2 GitHub
- CVE Record
How Was It Patched?
The maintainers updated the way exceptions are checked and sanitized when exiting the virtual machine. The patch ensures that no attacker-controlled exceptions can "leak" into the host and execute unintended code.
You can see the commit diff in the vm2 repository.
To fix
npm install vm2@latest
# or
yarn add vm2@latest
Always check your lockfiles and dependencies! Applications, plugins, or frameworks using vm2 should be audited.
Always audit your dependencies, especially when dealing with untrusted code.
Stay secure, and don’t let attackers out of the sandbox!
*Exclusive analysis by DevSec. Reach out for more post-mortems and in-depth writeups on JavaScript security.*
Timeline
Published on: 04/17/2023 22:15:00 UTC
Last modified on: 04/28/2023 01:13:00 UTC