Jan 30, 2026·6 min read·28 visits
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.
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 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.
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 /.
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:
ws://target/docker-container-terminal.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"
docker exec ... sh runs. It might fail or succeed, we don't care.; tells bash "finished with that, what's next?"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.
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.
If you are running Dokploy versions prior to 0.26.6, stop reading this and update. Now.
Update your Dokploy instance via the manager or pull the latest Docker image:
docker pull dokploy/dokploy:latest
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.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:L| Product | Affected Versions | Fixed Version |
|---|---|---|
Dokploy Dokploy | < 0.26.6 | 0.26.6 |
| Attribute | Detail |
|---|---|
| CVE ID | CVE-2026-24841 |
| CVSS | 9.9 (Critical) |
| CWE | CWE-78 (OS Command Injection) |
| Attack Vector | Network (Authenticated) |
| Impact | Remote Code Execution (RCE) as Host User |
| Fix Version | 0.26.6 |
Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')
CVE-2026-48153 is a Server-Side Request Forgery (SSRF) vulnerability in the Budibase OAuth2 SDK prior to version 3.39.0. It allows authenticated low-privileged users to bypass outbound network security blacklists and send arbitrary requests to internal subnets or cloud metadata services.
The self-hosted Slack Nebula VPN control plane, nebula-mesh, stored high-privilege enrollment tokens in plaintext inside its SQLite database. This flaw allowed any adversary with read access to the database to retrieve pending tokens and enroll unauthorized hosts into the secure VPN mesh.
The devbridge-autocomplete package (jQuery-Autocomplete) fails to escape category headers and suggestion values when using default formatters formatGroup and formatResult. If suggestions contain untrusted input, arbitrary HTML and JavaScript execute directly in the victim's browser session.
OpenCTI versions prior to 6.1.9 fail to properly restrict GraphQL schema introspection queries due to a weak pattern-matching implementation. An unauthenticated attacker can bypass the introspection block list by stripping whitespace and carriage returns, enabling complete reconnaissance of the GraphQL schema.
An unrestricted file upload vulnerability in Paymenter's support ticket system (prior to version 1.2.11) allows authenticated users to upload arbitrary PHP scripts to a web-accessible directory. The application fails to validate file extensions or MIME types before storing the files, enabling remote code execution under the web server's privilege context.
A technical analysis of CVE-2026-21887, a Server-Side Request Forgery (SSRF) vulnerability in OpenCTI. The flaw occurs in the platform's data ingestion mechanism, which processes user-supplied feed URLs via Axios under a default configuration. Authenticated users with low privileges can exploit this to pivot into internal infrastructure, target metadata services, and scan private networks.