Dozzle Dazzled: Skipping the Velvet Rope with CVE-2026-24740
Jan 27, 2026·5 min read·3 visits
Executive Summary (TL;DR)
Dozzle v9.0.2 implemented access control on the front door but left the back window open. While the UI filtered which containers you could see in a list, the backend API for retrieving a specific container by ID completely ignored your access permissions. This allows any authenticated user to access, view logs, and execute commands on any container managed by Dozzle agents, regardless of their assigned restrictions.
A critical authorization bypass in Dozzle's agent service allows restricted users to access any container by ID, effectively rendering Label-Based Access Control (LBAC) useless for direct lookups.
The Hook: Who Watches the Watchmen?
Dozzle is the darling of the Docker world for developers who want a lightweight, no-nonsense log viewer. It’s fast, it’s real-time, and it doesn't require a database. But like any tool that sits on top of the Docker socket, it wields immense power. If you control Dozzle, you control the containers. To mitigate this risk in multi-tenant or multi-team environments, Dozzle introduced Label-Based Access Control (LBAC).
The idea is simple: You tag your containers with labels like env=dev or env=prod. You then tell Dozzle, "User Alice is only allowed to see containers where env=dev." In theory, this keeps Alice from accidentally (or intentionally) rm -rf /'ing the production database. It’s a velvet rope that separates the interns from the critical infrastructure.
But here’s the thing about velvet ropes: they only work if the bouncer actually checks who walks past them. CVE-2026-24740 is the story of a bouncer who was handed a list of banned guests, nodded politely, and then let everyone in anyway because they knew the secret password (the Container ID).
The Flaw: A Tale of Two Functions
The vulnerability lies deep within the agentService logic. Dozzle supports a distributed mode where a master instance talks to agents running on other nodes. When a user logs in, their session is stamped with specific filter rules (the LBAC labels).
When the UI requests a list of containers, the system correctly applies these filters. Alice only sees her dev containers. The UI looks secure. The list is curated. Everything seems fine.
However, when the UI (or an attacker) requests a specific container by its ID—for example, to stream logs or start an interactive shell—the application calls a method named FindContainer. This method accepts the Container ID and the User's Labels. The expectation is that the method will say, "Okay, find container X, but only if it matches these labels."
In reality, the code treated the labels like a suggestion. The implementation for the agent service accepted the labels as an argument but failed to pass them along in the gRPC request to the remote agent. The agent, which has no knowledge of the user's session state, simply received a command: "Give me the handle for Container ID prod-db-123." Being a compliant piece of software, it obliged.
The Code: The Smoking Gun
Let's look at the Go code responsible for this mishap. This is a textbook example of a function signature promising security checks that the implementation fails to deliver.
The Vulnerable Code (v9.0.2):
In internal/support/container/agent_service.go, the FindContainer method takes labels as an argument, but watch where that variable goes (spoiler: nowhere).
func (a *agentService) FindContainer(ctx context.Context, id string, labels container.ContainerLabels) (container.Container, error) {
// The 'labels' parameter contains the security restrictions.
// Notice how it is completely ignored in the function call below?
return a.client.FindContainer(ctx, id)
}The Fix (v9.0.3): The patch involves properly serializing these labels into the gRPC request so the agent can enforce the filter. The developer had to update the client definition to accept the filter and pack it into the protobuf message.
// internal/agent/client.go in commit 620e59a
func (c *Client) FindContainer(ctx context.Context, id string, labels container.ContainerLabels) (container.Container, error) {
in := &pb.FindContainerRequest{ContainerId: id}
// Now we actually use the labels!
if labels != nil {
in.Filter = make(map[string]*pb.RepeatedString)
for k, v := range labels {
in.Filter[k] = &pb.RepeatedString{Values: v}
}
}
return c.client.FindContainer(ctx, in)
}This is a classic "Interface Disconnect." The architect likely designed the interface to be secure, but the plumbing didn't connect the pipes.
The Exploit: Guessing the Magic Number
Exploiting this requires an authenticated session, but it doesn't require high privileges. You just need to be a user with some access. Here is how a malicious "intern" could pivot to "admin":
- Reconnaissance: The attacker logs into Dozzle. They see only their boring
dev-appcontainers. They want to accessprod-payment-gateway. - ID Harvesting: The attacker needs the Container ID. Docker IDs are long hashes, but they are often predictable if short IDs are used, or they might leak in shared logs, error messages, or even source code (e.g., in a
docker-compose.ymlfile committed to a shared repo). - Direct Object Reference: The attacker crafts a request to the Dozzle API, bypassing the UI.
# Standard request to attach to a stream
GET /api/logs/stream/e3b0c44298fc?stdout=true&stderr=true HTTP/1.1
Cookie: auth=... (Valid Token with 'dev' scope)Because the backend FindContainer logic drops the 'dev' scope requirement when talking to the agent, the agent returns the stream for e3b0c44298fc (the Prod DB). The attacker now has a live view of production logs.
Escalation to RCE:
Dozzle isn't just a viewer. It supports exec. If the attacker sends a request to /api/containers/e3b0c44298fc/exec, the same flaw applies. They now have a root shell inside the production container.
The Impact: From Logs to Ransomware
This vulnerability turns a "Least Privilege" model into a "Most Privilege" suggestion. In many DevOps environments, developers are given read-only access to logs to debug issues. This CVE upgrades that access to full interactive control.
With exec access to a container, an attacker can:
- Steal Secrets: Read environment variables containing API keys, database credentials, and cloud tokens.
- Pivot Network: Use the container as a beachhead to scan the internal network.
- Data Exfiltration: Dump the database if the container has access to it.
- Denial of Service: Stop the container or corrupt the application state.
Additionally, a secondary flaw fixed in the same release (Commit 58caca2) involved a log.Fatal call when parsing invalid filters. This meant that even if an attacker couldn't guess an ID, they could crash the entire Dozzle service just by sending a malformed header, denying access to everyone else.
Official Patches
Fix Analysis (2)
Technical Appendix
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:HAffected Systems
Affected Versions Detail
| Product | Affected Versions | Fixed Version |
|---|---|---|
Dozzle amir20 | <= 9.0.2 | 9.0.3 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-285 |
| Attack Vector | Network |
| CVSS | 8.8 (High) |
| Impact | Confidentiality, Integrity, Availability |
| Exploit Status | PoC Available |
| Architecture | Agent/Client gRPC |
MITRE ATT&CK Mapping
Improper Authorization
Known Exploits & Detection
Vulnerability Timeline
Subscribe to updates
Get the latest CVE analysis reports delivered to your inbox.