CVE-2026-24841

PaaS-word to Pwnage: Breaking Dokploy with WebSocket Command Injection

Amit Schendel
Amit Schendel
Senior Security Researcher

Jan 30, 2026·6 min read·2 visits

Executive Summary (TL;DR)

Dokploy < 0.26.6 lets authenticated users pass unsanitized input into a shell command intended to spawn a Docker terminal. This allows attackers to break out of the `docker exec` command and run arbitrary code on the host server as root (or the service user). Fix: Upgrade to 0.26.6.

A critical command injection vulnerability in Dokploy's WebSocket terminal endpoint allows authenticated users to escape the confines of a Docker container session and execute arbitrary commands on the host operating system. By manipulating the parameters sent during the WebSocket handshake, attackers can abuse the server's use of shell interpolation to hijack the underlying process spawning logic.

The Hook: Your Cloud, My Command

PaaS (Platform as a Service) solutions like Dokploy are the darlings of the modern developer stack. They promise the ease of Heroku with the cost-savings of self-hosting. You give them a server, they give you a dashboard to deploy your Next.js apps and Postgres databases with a single click. It's magic. But as any stage magician knows, the trick is usually just hiding the messy reality behind a curtain.

One of the coolest features of these dashboards is the ability to shell directly into your running containers from the browser. It feels powerful. It feels like ssh, but over HTTP. However, implementing a web-based terminal is surprisingly treacherous territory. It requires taking user input from a WebSocket, piping it into a pseudo-terminal (PTY) on the server, and connecting that to a process.

Dokploy's implementation of this feature, specifically in the /docker-container-terminal endpoint, decided to take a shortcut through the most dangerous neighborhood in code-town: sh -c. Instead of carefully handling process arguments, it trusted the user to play nice. Spoiler alert: users do not play nice.

The Flaw: A Classic Shell Game

The vulnerability lives in how Dokploy spawns the interactive terminal session. When you click that "Terminal" tab in the UI, the frontend opens a WebSocket connection. During this handshake, it sends two key pieces of information: the containerId (which container do you want to enter?) and the activeWay (what shell do you want to run, like /bin/bash or /bin/sh).

In a secure world, the server would say, "Okay, I will run the binary docker with the arguments exec, -it, [containerId], and [shell]." This treats the arguments as raw strings.

But Dokploy didn't do that. Instead, it constructed a string using template literals—Javascript's way of gluing text together. It built a command string like this:

docker exec -it -w / ${containerId} ${activeWay}

And then—this is the kill shot—it passed that entire string to a shell for execution. The code effectively did bash -c "docker exec ...".

If you know anything about bash, you know it treats certain characters as control instructions. Semicolons, ampersands, backticks—these aren't just text to bash; they are orders. By failing to sanitize the input or use an argument array, Dokploy allowed parameters to become instructions.

The Code: Anatomy of a Disaster

Let's look at the crime scene. The vulnerable code was located in apps/dokploy/server/wss/docker-container-terminal.ts. Here is the logic that accepted the WebSocket connection and spawned the process.

The Vulnerable Code (Pre-0.26.6):

// The inputs come directly from the request
const { containerId, activeWay } = query;
 
// ... 
 
const shell = getShell(); // Returns the system shell
// THE SMOKING GUN:
const ptyProcess = spawn(
    shell,
    // This array implies the first arg is '-c' and the second is the command string
    ["-c", `docker exec -it -w / ${containerId} ${activeWay}`],
    {},
);

See that ${containerId}? That is unadulterated user input being pasted directly into a command string that shell is about to parse. The developer assumed containerId would be a hex string like a1b2c3d4. But what if it isn't?

The Fix (Commit 74e0bd5fe3ef7199f44fcd19c6f5a2f09b806d6f):

The patch does two things: it validates the input format (regex police!) and, more importantly, it stops using the shell to spawn the process.

// 1. Validation
if (!isValidContainerId(containerId)) {
    ws.close(4000, "Invalid container ID format");
    return;
}
 
// 2. Safe Spawn (No shell interpolation)
const ptyProcess = spawn(
    "docker", // Executing the binary directly
    ["exec", "-it", "-w", "/", containerId, shell], // Arguments as an array
    {},
);

By passing arguments as an array, docker receives the inputs literally. Even if you somehow bypassed the regex and passed ; rm -rf /, the docker binary would just complain that it can't find a container named ; rm -rf /.

The Exploit: Escaping the Sandbox

Exploiting this requires authentication, but "authenticated" in a multi-tenant or team environment is a low bar. Once we have a session, we can manually initiate a WebSocket connection. We don't need to use the UI; we can use wscat or a custom Python script.

The Attack Chain:

  1. Login: Obtain a valid session cookie or JWT.
  2. Connect: Open a WebSocket to ws://target/docker-container-terminal.
  3. Inject: We manipulate the query parameters. We can inject into containerId or activeWay.

Payload Construction:

If we send containerId as $(id) and activeWay as sh, the server executes: bash -c "docker exec -it -w / $(id) sh"

The shell sees $() and executes the id command on the host before passing the result to docker. That's cute, but we want more. We want to break the chain.

The "Game Over" Payload:

Let's set activeWay to sh; cat /etc/passwd. The server executes: bash -c "docker exec -it -w / [id] sh; cat /etc/passwd"

  1. docker exec ... sh runs. It might fail or succeed, we don't care.
  2. ; tells bash "finished with that, what's next?"
  3. cat /etc/passwd runs on the host server.

Because Dokploy is a management tool, it likely runs as root (to control the Docker daemon) or a highly privileged user. This is a full host takeover. We aren't stuck inside the container; we are on the captain's bridge.

The Impact: Why Severity is 9.9

You might wonder, "If I need to be authenticated, why is this a 9.9 and not an 8.something?" The answer lies in the Scope Change (S:C) metric of CVSS.

The vulnerability exists in the application layer (Dokploy), but the impact affects the underlying operating system (the Host). The attacker breaks out of the security context of the web application and gains control over the infrastructure itself.

Furthermore, in many deployment scenarios, developers might share a Dokploy instance. If a disgruntled contractor or a compromised junior dev account exploits this, they don't just mess up their own project; they own the entire server. They can dump the databases of other projects, steal environment variables (AWS keys, anyone?), install crypto miners, or pivot to the internal network.

It is effectively a "God Mode" cheat code for anyone with a login.

The Fix: Remediation

If you are running Dokploy versions prior to 0.26.6, stop reading this and update. Now.

Official Fix

Update your Dokploy instance via the manager or pull the latest Docker image: docker pull dokploy/dokploy:latest

Temporary Mitigation

If you cannot update immediately (why?), you must restrict access to the dashboard. Use a WAF or a reverse proxy (like Nginx) to block requests to /docker-container-terminal that contain suspicious characters ($, ;, &, |) in the query string.

However, WAF rules are notoriously bypassable. The only real fix is code that doesn't treat user input like a trusted friend. Trust no one, especially not a string from the internet.

Fix Analysis (1)

Technical Appendix

CVSS Score
9.9/ 10
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:L
EPSS Probability
0.25%
Top 52% most exploited

Affected Systems

Dokploy < 0.26.6

Affected Versions Detail

Product
Affected Versions
Fixed Version
Dokploy
Dokploy
< 0.26.60.26.6
AttributeDetail
CVE IDCVE-2026-24841
CVSS9.9 (Critical)
CWECWE-78 (OS Command Injection)
Attack VectorNetwork (Authenticated)
ImpactRemote Code Execution (RCE) as Host User
Fix Version0.26.6
CWE-78
OS Command Injection

Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')

Vulnerability Timeline

Vulnerability fixed in commit 74e0bd5
2026-01-27
CVE-2026-24841 Published
2026-01-28
GHSA-vx6x-6559-x35r Published
2026-01-28

Subscribe to updates

Get the latest CVE analysis reports delivered to your inbox.