← All articles

From One Wazuh Agent to the Whole Fleet

Two Wazuh reports exposed a path from one enrolled agent to a root write on the manager, then root execution across the agents it controls.

Product
Wazuh manager and agent
Affected versions
Manager >= 4.1.0; agent >= 4.3.0
Fixed versions
4.14.8
Status
Patched

A Wazuh agent is supposed to send events to its manager. It is not supposed to issue upgrade commands to that manager. While tracing the agent upgrade path, I found that an agent could do exactly that.

On July 24, 2026, I sent Wazuh two reports. One showed how a manager could run commands as root on its agents despite the default SCA restriction. The other showed how a single enrolled agent could turn an upgrade request into an arbitrary file write as root on the manager. I reported the latter as a route to manager-side code execution. The two findings fit together:

one controlled agent
    -> root write on the manager
    -> root execution on the manager
    -> root command execution on agents managed by it

This is the story of those two reports, the code paths behind them, and a gap between what Wazuh confirmed privately and what the public manager advisory describes.

Where the agent crossed into manager authority

The first interesting line is in the agent message path. After wazuh-remoted decrypts an agent message, SendMSGAction uses its first byte as the queue class. An agent can choose u, the UPGRADE_MQ class. wazuh-analysisd forwards that JSON to the upgrade module’s command socket. The same socket also receives requests originating from the management API, but the module did not distinguish the two sources. It runs within wazuh-modulesd as root. The published root-cause analysis for CVE-2026-74034 describes this path.

That one-byte routing choice changed the meaning of the message. An agent event became a request to the manager’s administrative upgrade machinery. The API’s input validation did not help because the agent channel reached the module without passing through the API.

The other researcher’s CVE-2026-74034 advisory demonstrates a clean consequence: send upgrade_custom with an attacker-chosen file_path, and the manager opens that file as root and sends its contents back to the agent. Their reproduction retrieves /etc/shadow and client.keys. It is a real, serious file-read vulnerability.

My report took the ordinary upgrade branch instead. analysisd appended the sender’s ID to the request’s agents list, but the upgrade parser detached the first agents key it found. An agent-supplied key placed earlier in the JSON won over the appended one. That let my request name the upgrade target.

Where the URL became a destination

The ordinary upgrade flow constructs a package name from the requested version and downloads it from wpk_repo. The manager sends a pipe-delimited command to its download helper:

download <url>|<destination>|<header>|<data>|<timeout>|

In wurl_request(), the destination is escaped for the pipe delimiter. The URL is not. The relevant logic is short:

_url = wstr_replace(url, " ", "%20");
parsed_dest = wstr_replace(dest, "|", "\\|");
snprintf(srequest, zrequest, "%s %s|%s|%s|%s|%ld|", ...);

A pipe in the agent-supplied version survives into the URL. When wm_download parses the request, that pipe ends the URL field and shifts the following text into the destination field. The helper rejects a parent-directory reference such as ../, but it does not reject an absolute path. It opens the parsed destination with wfopen(..., "wb") and writes the fetched bytes there as root. The attacker-controlled repository supplies those bytes. (Package URL construction, request formatting, download parsing.)

There is a tempting objection: Wazuh checks the WPK hash. But the check happens after the download helper has written the file. Worse for this case, it checks the package at the intended cache path, while the injected delimiter has made the helper write somewhere else. The upgrade may eventually fail validation; the root-owned write has already occurred. That ordering is visible in the vulnerable upgrade validation code.

This was the manager-side primitive in my report: attacker-chosen contents written to an attacker-chosen absolute path, subject to normal filesystem constraints. A path later consumed by a root-run service makes that a code-execution route. I submitted a working PoC and described the impact as manager-side RCE.

Why manager compromise reached the fleet

My other report looked in the opposite direction. Wazuh’s Security Configuration Assessment (SCA) policies can contain command checks. The agent normally refuses command checks supplied by the manager when sca.remote_commands is disabled, which is the default. The decision should depend on the origin of the policy. In the vulnerable code, it depended on the policy file’s resolved path:

policy->remote = strstr(realpath_buffer, "etc/shared/") != NULL;

If the path did not contain etc/shared/, Wazuh treated the policy as local and allowed its command checks to run. But a manager could transfer an unsigned policy into the agent’s var/incoming/ directory through the upgrade file channel, then reference that file from the manager-supplied agent.conf. The policy was remote in every meaningful sense; its location made it look local to the SCA gate. The CVE-2026-74036 advisory shows a command check running as uid=0(root) on the agent, without a WPK signing key.

The two issues therefore compose. After taking the manager, an attacker can use its shared configuration and unsigned file-transfer capability to execute commands on agents that receive and apply that configuration. I am describing a reachable fleet-wide chain, not claiming that offline agents execute instantly or that I tested every agent in a deployment. The agent advisory states the scope as any agent whose shared configuration the manager controls.

The duplicate decision

Wazuh’s reply to my July 24 manager-side report is unusually specific. The maintainer said they had followed my working PoC end to end against v4.14.6. Their response identified the agent-controlled routing byte, the missing origin check, the duplicate agents key, the unescaped pipe in the version URL, and the root write through wm_download. They closed my report as a duplicate because the missing origin check had already been reported by Jan Kahmen and addressed in PR #37745. That PR was merged on July 22, two days before my submission.

Redacted July 24 exchange with Wazuh confirming the root-write chain and showing my follow-up

The July 24 exchange. The maintainer confirmed the chain; I accepted the duplicate closure and asked how operators would learn about the full impact.

I replied that the duplicate decision made sense because limiting agent traffic to upgrade_update_status cut off my PoC. I asked whether the root-write path would receive a CVE and advisory so operators running 4.14.6 and 4.14.7 could prioritize the upgrade. I also pointed out that the delimiter flaw in wurl_request remained: the routing fix removed my agent-controlled path, but did not change how the download request was parsed. The other callers I identified used administrator-configured URLs, so I presented those as a defense-in-depth concern rather than another demonstrated attacker route.

PR #37745 allows only upgrade_update_status from the agent channel. It drops upgrade and upgrade_custom before they reach the upgrade module. That is the right place to enforce who may issue those commands. The patch closes my agent-to-manager entry point; it is not evidence that the download helper’s pipe-delimited format was itself changed.

What I find harder to accept is the public description. Wazuh published CVE-2026-74034 on October 13 with the title “Arbitrary file read as root” and a 7.5 score. The title is accurate for the file-read PoC in that advisory. It omits the root-write path Wazuh told me it had reproduced from the same unauthorized-command entry point when combined with the delimiter bug. I regard that public framing as misleadingly narrow. The correspondence establishes knowledge and reproduction of the write chain; it does not tell me the reason for the editorial choice.

The scoring difference makes the omission visible. The published read-only vector is CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N. A completed write-to-root-execution chain would instead support high confidentiality, integrity, and availability impact: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H, 9.8 (Critical) in the default passwordless-enrollment scenario when the agent-facing ports are reachable. Requiring an enrollment secret or an existing agent key changes the privileges assumption to PR:L and makes that combined vector 8.8. The 9.8 is my assessment of the combined chain, not the vendor’s score for the published file-read PoC. Whether that impact belongs under the same CVE or a separate one is a grouping question; the missing public explanation is the problem. (FIRST CVSS v3.1 specification, Wazuh manager advisory.)

Fixes and disclosure timeline

Wazuh corrected the agent-message origin check in PR #37745 and the SCA policy-origin decision in PR #37953. Both fixes shipped in 4.14.8. These are distinct trust decisions: who may request an upgrade and who supplied an SCA policy.

Date Event
July 22, 2026 Upgrade-command origin fix merged in PR #37745.
July 24, 2026 I submitted both reports; Wazuh closed the manager-side report as a duplicate and confirmed the root-write chain in its response.
July 30, 2026 SCA policy-origin fix merged in PR #37953.
September 25, 2026 Wazuh released 4.14.8.
October 13, 2026 Wazuh published the agent RCE and manager file-read advisories.

The manager-side report did not receive its own CVE. The practical result of the duplicate closure was a fix. The cost was a public record that describes only the file-read branch of a trust-boundary failure Wazuh had also seen produce a root write. For defenders evaluating one compromised agent against a Wazuh deployment, that distinction changes the story.

References