rapid7/metasploit-framework

View on GitHub
lib/msf/core/exploit/cacti.rb

Summary

Maintainability
A
25 mins
Test Coverage
# -*- coding: binary -*-
###
#
# This mixin provides helper methods for Cacti
#
###

module Msf
module Exploit::Cacti

  include Msf::Exploit::Remote::HttpClient

  class CactiError < StandardError; end
  class CactiNotFoundError < CactiError; end
  class CactiVersionNotFoundError < CactiError; end
  class CactiNoAccessError < CactiError; end
  class CactiCsrfNotFoundError < CactiError; end
  class CactiLoginError < CactiError; end

  # Extract the version number from an HTML response
  #
  # @param html [Nokogiri::HTML::Document] The HTML response
  # @return [String] The version number
  # @raise [CactiNotFoundError] If the web server is not running Cacti
  # @raise [CactiVersionNotFoundError] If the version string was not found
  def parse_version(html)
    # This will return an empty string if there is no match
    version_str = html.xpath('//div[@class="versionInfo"]').text
    unless version_str.include?('The Cacti Group')
      raise CactiNotFoundError, 'The web server is not running Cacti'
    end
    unless version_str.match(/Version (?<version>\d{1,2}[.]\d{1,2}[.]\d{1,2})/)
      raise CactiVersionNotFoundError, 'Could not detect the version'
    end

    Regexp.last_match[:version]
  end

  # Extract the CSRF token from an HTML response
  #
  # @param html [Nokogiri::HTML::Document] The HTML response to parse
  # @return [String] The CSRF token
  def parse_csrf_token(html)
    html.xpath('//form/input[@name="__csrf_magic"]/@value').text
  end

  # Get the CSRF token by querying the `index.php` web page and extracting it
  # from the response.
  #
  # @return [String] The CSRF token
  # @raise [CactiNoAccessError] If the server is unreachable
  # @raise [CactiCsrfNotFoundError] If it was not possible to get the CSRF token
  def get_csrf_token
    res = send_request_cgi(
      'uri' => normalize_uri(target_uri.path, 'index.php'),
      'method' => 'GET',
      'keep_cookies' => true
    )
    raise CactiNoAccessError, 'Could not access `index.php` - no response' if res.nil?

    html = res.get_html_document
    csrf_token = parse_csrf_token(html)
    raise CactiCsrfNotFoundError, 'Unable to get the CSRF token' if csrf_token.empty?

    csrf_token
  end

  # Log in to Cacti. It will take care of grabbing the CSRF token if not provided.
  #
  # @param username [String] The username
  # @param password [String] The password
  # @raise [CactiNoAccessError] If the server is unreachable
  # @raise [CactiCsrfNotFoundError] If the CSRF token was not provided and it was not possible to retrieve it
  # @raise [CactiLoginError] If the login failed
  def do_login(username, password, csrf_token: nil)
    if csrf_token.blank?
      print_status('Getting the CSRF token to login')
      begin
        csrf_token = get_csrf_token
      rescue CactiError => e
        raise CactiLoginError, "Unable to login: #{e.class} - #{e}"
      end

      vprint_good("CSRF token: #{csrf_token}")
    end

    print_status("Attempting login with user `#{username}` and password `#{password}`")
    res = send_request_cgi(
      'uri' => normalize_uri(target_uri.path, 'index.php'),
      'method' => 'POST',
      'keep_cookies' => true,
      'vars_post' => {
        '__csrf_magic' => csrf_token,
        'action' => 'login',
        'login_username' => username,
        'login_password' => password
      }
    )
    raise CactiNoAccessError, 'Could not login - no response' if res.nil?
    raise CactiLoginError, "Login failure - unexpected HTTP response code: #{res.code}" unless res.code == 302

    print_good('Logged in')

    nil
  end

end
end