Mar 5, 2026·5 min read·3 visits
Docker CLI for Windows insecurely searches for plugins in a user-writable directory under `%ProgramData%`. A local attacker can create this directory and plant a malicious binary (e.g., `docker-compose.exe`). When a victim runs Docker commands, the malicious binary executes with the victim's privileges, leading to privilege escalation.
A critical Local Privilege Escalation (LPE) vulnerability affects Docker CLI for Windows, stemming from an insecure plugin search path in the `C:\ProgramData` directory. Due to permissive default Access Control Lists (ACLs) on Windows, low-privileged users can create subdirectories within `ProgramData`. The Docker CLI plugin manager inadvertently trusts this location, allowing attackers to plant malicious executables that are subsequently executed by privileged users during standard Docker operations.
CVE-2025-15558 represents a classic Uncontrolled Search Path Element vulnerability (CWE-427) within the Docker CLI for Windows. The flaw exists in the plugin discovery mechanism, which is responsible for locating and executing external CLI extensions such as docker-compose and docker-buildx.
By default, the Docker CLI plugin manager is configured to scan specific system directories for executable plugins. Prior to version 29.2.0, this search list included C:\ProgramData\Docker\cli-plugins. While this path appears legitimate, the C:\ProgramData root directory on Windows systems possesses default Access Control Lists (ACLs) that allow the BUILTIN\Users group to create new subdirectories. Since the Docker installer does not strictly manage or pre-create this specific subdirectory with restricted permissions, the path remains open to manipulation.
The vulnerability allows a low-privileged local attacker to create the missing directory structure and deposit malicious binaries. When a privileged user or an automated service account subsequently invokes a Docker command that triggers a plugin lookup, the application loads and executes the attacker's binary instead of the legitimate plugin. This results in arbitrary code execution with the privileges of the victim user.
The root cause of CVE-2025-15558 lies in the incorrect assumption that %ProgramData% is a trusted location for executable code without explicit ACL enforcement. In the Windows security model, %ProgramFiles% is protected by default, requiring Administrator privileges to write or modify content. Conversely, %ProgramData% is designed to hold application data and allows standard users FILE_ADD_SUBDIRECTORY permissions on the root folder.
The Docker CLI codebase, specifically within the cli-plugins/manager package, defined a slice of default search paths for Windows. This slice included both the secure %ProgramFiles% path and the insecure %ProgramData% path. When the CLI initializes, it iterates through these paths looking for binaries matching the plugin naming convention (e.g., docker-myplugin.exe).
The vulnerability is compounded by the fact that the specific target directory Docker\cli-plugins often does not exist on a default installation. This absence allows an attacker to create the directory themselves. Because the creator of a directory typically inherits ownership or write permissions, the attacker can then populate this directory with arbitrary executables. The Docker CLI, lacking signature verification for plugins in this path, proceeds to execute the binary found during its discovery phase.
The vulnerability was located in cli-plugins/manager/manager_windows.go. The code defined a defaultSystemPluginDirs variable, which served as the source of truth for plugin discovery locations.
Below is the analysis of the fix implemented in commit 13759330b1f7e7cb0d67047ea42c5482548ba7fa. The remediation strategy was straightforward: remove the insecure %ProgramData% path from the search list entirely.
// cli-plugins/manager/manager_windows.go
// VULNERABLE CODE (Pre-patch)
// The slice includes the ProgramData path, which is writable by standard users.
var defaultSystemPluginDirs = []string{
filepath.Join(os.Getenv("ProgramData"), "Docker", "cli-plugins"), // <--- REMOVED
filepath.Join(os.Getenv("ProgramFiles"), "Docker", "cli-plugins"),
}
// PATCHED CODE (Fixed)
// The slice now only includes ProgramFiles, which is restricted to Administrators.
var defaultSystemPluginDirs = []string{
filepath.Join(os.Getenv("ProgramFiles"), "Docker", "cli-plugins"),
}By removing the entry referencing os.Getenv("ProgramData"), the developers effectively eliminated the attack surface. The CLI now strictly looks for system-wide plugins in %ProgramFiles%, where standard users cannot introduce malicious files.
Exploiting this vulnerability requires local access to the target machine but does not require elevated privileges. The attack follows a standard "binary planting" or "DLL hijacking" pattern, adapted for executable search paths.
1. Reconnaissance
The attacker checks for the existence of the directory C:\ProgramData\Docker\cli-plugins. If it is missing (which is common), the system is vulnerable.
2. Pre-creation
The attacker creates the directory structure C:\ProgramData\Docker\cli-plugins. Due to default Windows ACLs, any authenticated user can create folders in C:\ProgramData.
3. Weaponization
The attacker compiles a malicious executable. To maximize impact, they will name it after a common Docker plugin, such as docker-compose.exe, docker-buildx.exe, or docker-scan.exe.
4. Execution
The attacker waits for a privileged user (e.g., a developer or administrator) to use the Docker CLI. When the victim runs a command like docker compose up, the CLI searches the plugin paths. Finding the attacker's binary in ProgramData first, it executes the malicious code with the victim's token.
The primary impact of CVE-2025-15558 is Local Privilege Escalation (LPE). In corporate environments, Docker is typically utilized by developers and DevOps engineers who often possess local Administrator privileges or have access to sensitive credentials (SSH keys, cloud API tokens, source code repositories).
Successful exploitation grants the attacker the same privilege level as the victim user. If the victim is an Administrator, the attacker gains full control over the host system. Even if the victim is a standard user, the attacker can access that user's Docker context, potentially allowing them to deploy malicious containers, access mounted volumes, or pivot to other systems accessible via the Docker network overlay.
Furthermore, this vulnerability can be used for Persistence. By planting a malicious plugin, an attacker ensures their code is re-executed every time legitimate development work occurs, making detection difficult without specific file integrity monitoring.
CVSS:4.0/AV:L/AC:L/AT:N/PR:L/UI:P/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/AU:N/R:U| Product | Affected Versions | Fixed Version |
|---|---|---|
Docker CLI Docker | <= 29.1.5 | 29.2.0 |
Docker Desktop Docker | < 4.37.0 | 4.37.0 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-427 |
| Attack Vector | Local |
| CVSS v4.0 | 7.0 (High) |
| CVSS Vector | CVSS:4.0/AV:L/AC:L/AT:N/PR:L/UI:P/VC:H/VI:H/VA:H |
| Exploit Status | PoC Ready |
| KEV Status | Not Listed |
Uncontrolled Search Path Element