Lucene search
K

Multi Recon Local Exploit Suggester

🗓️ 11 Aug 2015 16:51:52Reported by sinn3r <[email protected]>, MoType 
metasploit
 metasploit
🔗 www.rapid7.com👁 66 Views

This module suggests local meterpreter exploits based on architecture, platform, and available exploits. Not all local exploits will be fired, based on session type, platform, architecture, and required default options

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

class MetasploitModule < Msf::Post

  include Msf::Auxiliary::Report

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Multi Recon Local Exploit Suggester',
        'Description' => %q{
          This module suggests local meterpreter exploits that can be used.

          The exploits are suggested based on the architecture and platform
          that the user has a shell opened as well as the available exploits
          in meterpreter.

          It's important to note that not all local exploits will be fired.
          Exploits are chosen based on these conditions: session type,
          platform, architecture, and required default options.
        },
        'License' => MSF_LICENSE,
        'Author' => [ 'sinn3r', 'Mo' ],
        'Platform' => all_platforms,
        'SessionTypes' => [ 'meterpreter', 'shell' ]
      )
    )
    register_options [
      Msf::OptInt.new('SESSION', [ true, 'The session to run this module on' ]),
      Msf::OptBool.new('SHOWDESCRIPTION', [true, 'Displays a detailed description for the available exploits', false])
    ]

    register_advanced_options(
      [
        Msf::OptBool.new('ValidateArch', [true, 'Validate architecture', true]),
        Msf::OptBool.new('ValidatePlatform', [true, 'Validate platform', true]),
        Msf::OptBool.new('ValidateMeterpreterCommands', [true, 'Validate Meterpreter commands', false]),
        Msf::OptString.new('Colors', [false, 'Valid, Invalid and Ignored colors for module checks (unset to disable)', 'grn/red/blu'])
      ]
    )
  end

  def all_platforms
    Msf::Module::Platform.subclasses.collect { |c| c.realname.downcase }
  end

  def session_arch
    # Prefer calling native arch when available, as most LPEs will require this (e.g. x86, x64) as opposed to Java/Python Meterpreter's values (e.g. Java, Python)
    session.respond_to?(:native_arch) ? session.native_arch : session.arch
  end

  def is_module_arch?(mod)
    mod_arch = mod.target.arch || mod.arch
    mod_arch.include?(session_arch)
  end

  def is_module_wanted?(mod)
    mod[:result][:incompatibility_reasons].empty?
  end

  def is_session_type?(mod)
    # There are some modules that do not define any compatible session types.
    # We could assume that means the module can run on all session types,
    # Or we could consider that as incorrect module metadata.
    mod.session_types.include?(session.type)
  end

  def is_module_platform?(mod)
    platform_obj = Msf::Module::Platform.find_platform session.platform
    return false if mod.target.nil?

    module_platforms = mod.target.platform ? mod.target.platform.platforms : mod.platform.platforms
    module_platforms.include? platform_obj
  rescue ArgumentError => e
    # When not found, find_platform raises an ArgumentError
    elog('Could not find a platform', error: e)
    return false
  end

  def has_required_module_options?(mod)
    get_all_missing_module_options(mod).empty?
  end

  def get_all_missing_module_options(mod)
    missing_options = []
    mod.options.each_pair do |option_name, option|
      missing_options << option_name if option.required && option.default.nil? && mod.datastore[option_name].blank?
    end
    missing_options
  end

  def valid_incompatibility_reasons(mod, verify_reasons)
    # As we can potentially ignore some `reasons` (e.g. accepting arch values which are, on paper, not compatible),
    # this keeps track of valid reasons why we will not consider the module that we are evaluating to be valid.
    valid_reasons = []
    valid_reasons << "Missing required module options (#{get_all_missing_module_options(mod).join('. ')})" unless verify_reasons[:has_required_module_options]

    incompatible_opts = []
    incompatible_opts << 'architecture' unless verify_reasons[:is_module_arch]
    incompatible_opts << 'platform' unless verify_reasons[:is_module_platform]
    incompatible_opts << 'session type' unless verify_reasons[:is_session_type]
    valid_reasons << "Not Compatible (#{incompatible_opts.join(', ')})" if incompatible_opts.any?

    valid_reasons << 'Missing/unloadable Meterpreter commands' if verify_reasons[:missing_meterpreter_commands].any?
    valid_reasons
  end

  def set_module_options(mod)
    ignore_list = ['ACTION', 'TARGET'].freeze
    datastore.each_pair do |k, v|
      mod.datastore[k] = v unless ignore_list.include?(k.upcase)
    end
    if !mod.datastore['SESSION'] && session.present?
      mod.datastore['SESSION'] = session.sid
    end
  end

  def set_module_target(mod)
    session_platform = Msf::Module::Platform.find_platform(session.platform)
    target_index = mod.targets.find_index do |target|
      # If the target doesn't define its own compatible platforms or architectures, default to the parent (module) values.
      target_platforms = target.platform&.platforms || mod.platform.platforms
      target_architectures = target.arch || mod.arch

      target_platforms.include?(session_platform) && target_architectures.include?(session_arch)
    end
    mod.datastore['Target'] = target_index if target_index
  end

  def setup
    return unless session

    print_status "Collecting local exploits for #{session.session_type}..."

    setup_validation_options
    setup_color_options

    # Collects exploits into an array
    @local_exploits = []
    exploit_refnames = framework.exploits.module_refnames
    exploit_refnames.each_with_index do |name, index|
      print "%bld%blu[*]%clr Collecting exploit #{index + 1} / #{exploit_refnames.count}\r"
      mod = framework.exploits.create name
      next unless mod

      set_module_options mod
      set_module_target mod

      verify_result = verify_mod(mod)
      @local_exploits << { module: mod, result: verify_result } if verify_result[:has_check]
    end
  end

  def verify_mod(mod)
    return { has_check: false } unless mod.is_a?(Msf::Exploit::Local) && mod.has_check?

    result = {
      has_check: true,
      is_module_platform: (@validate_platform ? is_module_platform?(mod) : true),
      is_module_arch: (@validate_arch ? is_module_arch?(mod) : true),
      has_required_module_options: has_required_module_options?(mod),
      missing_meterpreter_commands: (@validate_meterpreter_commands && session.type == 'meterpreter') ? meterpreter_session_incompatibility_reasons(session) : [],
      is_session_type: is_session_type?(mod)
    }
    result[:incompatibility_reasons] = valid_incompatibility_reasons(mod, result)
    result
  end

  def setup_validation_options
    @validate_arch = datastore['ValidateArch']
    @validate_platform = datastore['ValidatePlatform']
    @validate_meterpreter_commands = datastore['ValidateMeterpreterCommands']
  end

  def setup_color_options
    @valid_color, @invalid_color, @ignored_color =
      (datastore['Colors'] || '').split('/')

    @valid_color = "%#{@valid_color}" unless @valid_color.blank?
    @invalid_color = "%#{@invalid_color}" unless @invalid_color.blank?
    @ignored_color = "%#{@ignored_color}" unless @ignored_color.blank?
  end

  def show_found_exploits
    unless datastore['VERBOSE']
      print_status "#{@local_exploits.length} exploit checks are being tried..."
      return
    end

    vprint_status "The following #{@local_exploits.length} exploit checks are being tried:"
    @local_exploits.each do |x|
      vprint_status x[:module].fullname
    end
  end

  def run
    runnable_exploits = @local_exploits.select { |mod| is_module_wanted?(mod) }
    if runnable_exploits.empty?
      print_error 'No suggestions available.'
      vprint_line
      vprint_session_info
      vprint_status unwanted_modules_table(@local_exploits.reject { |mod| is_module_wanted?(mod) })
      return
    end

    show_found_exploits
    results = runnable_exploits.map.with_index do |mod, index|
      print "%bld%blu[*]%clr Running check method for exploit #{index + 1} / #{runnable_exploits.count}\r"
      begin
        checkcode = mod[:module].check
      rescue StandardError => e
        elog("#Local Exploit Suggester failed with: #{e.class} when using #{mod[:module].shortname}", error: e)
        vprint_error "Check with module #{mod[:module].fullname} failed with error #{e.class}"
        next { module: mod[:module], errors: ['The check raised an exception.'] }
      end

      if checkcode.nil?
        vprint_error "Check failed with #{mod[:module].fullname} for unknown reasons"
        next { module: mod[:module], errors: ['The check failed for unknown reasons.'] }
      end

      # See def is_check_interesting?
      unless is_check_interesting? checkcode
        vprint_status "#{mod[:module].fullname}: #{checkcode.message}"
        next { module: mod[:module], errors: [checkcode.message] }
      end

      # Prints the full name and the checkcode message for the exploit
      print_good "#{mod[:module].fullname}: #{checkcode.message}"

      # If the datastore option is true, a detailed description will show
      if datastore['SHOWDESCRIPTION']
        # Formatting for the description text
        Rex::Text.wordwrap(Rex::Text.compress(mod[:module].description), 2, 70).split(/\n/).each do |line|
          print_line line
        end
      end

      next { module: mod[:module], checkcode: checkcode.message }
    end

    print_line
    print_status valid_modules_table(results)

    vprint_line
    vprint_session_info
    vprint_status unwanted_modules_table(@local_exploits.reject { |mod| is_module_wanted?(mod) })

    report_data = []
    results.each do |result|
      report_data << [result[:module].fullname, result[:checkcode]] if result[:checkcode]
    end
    report_note(
      host: session.session_host,
      type: 'local.suggested_exploits',
      data: report_data
    )
  end

  def valid_modules_table(results)
    name_styler = ::Msf::Ui::Console::TablePrint::CustomColorStyler.new
    check_styler = ::Msf::Ui::Console::TablePrint::CustomColorStyler.new

    # Split all the results by their checkcode.
    # We want the modules that returned a checkcode to be at the top.
    checkcode_rows, without_checkcode_rows = results.partition { |result| result[:checkcode] }
    rows = (checkcode_rows + without_checkcode_rows).map.with_index do |result, index|
      color = result[:checkcode] ? @valid_color : @invalid_color
      check_res = result.fetch(:checkcode) { result[:errors].join('. ') }
      name_styler.merge!({ result[:module].fullname => color })
      check_styler.merge!({ check_res => color })

      [
        index + 1,
        result[:module].fullname,
        result[:checkcode] ? 'Yes' : 'No',
        check_res
      ]
    end

    Rex::Text::Table.new(
      'Header' => "Valid modules for session #{session.sid}:",
      'Indent' => 1,
      'Columns' => [ '#', 'Name', 'Potentially Vulnerable?', 'Check Result' ],
      'SortIndex' => -1,
      'WordWrap' => false, # Don't wordwrap as it messes up coloured output when it is broken up into more than one line
      'ColProps' => {
        'Name' => {
          'Stylers' => [name_styler]
        },
        'Potentially Vulnerable?' => {
          'Stylers' => [::Msf::Ui::Console::TablePrint::CustomColorStyler.new({ 'Yes' => @valid_color, 'No' => @invalid_color })]
        },
        'Check Result' => {
          'Stylers' => [check_styler]
        }
      },
      'Rows' => rows
    )
  end

  def unwanted_modules_table(unwanted_modules)
    arch_styler = ::Msf::Ui::Console::TablePrint::CustomColorStyler.new
    platform_styler = ::Msf::Ui::Console::TablePrint::CustomColorStyler.new
    session_type_styler = ::Msf::Ui::Console::TablePrint::CustomColorStyler.new

    rows = unwanted_modules.map.with_index do |mod, index|
      platforms = mod[:module].target.platform&.platforms&.any? ? mod[:module].target.platform.platforms : mod[:module].platform.platforms
      platforms ||= []
      arch = mod[:module].target.arch&.any? ? mod[:module].target.arch : mod[:module].arch
      arch ||= []

      arch.each do |a|
        if a != session_arch
          if @validate_arch
            color = @invalid_color
          else
            color = @ignored_color
          end
        else
          color = @valid_color
        end

        arch_styler.merge!({ a.to_s => color })
      end

      platforms.each do |module_platform|
        if module_platform != ::Msf::Module::Platform.find_platform(session.platform)
          if @validate_platform
            color = @invalid_color
          else
            color = @ignored_color
          end
        else
          color = @valid_color
        end

        platform_styler.merge!({ module_platform.realname => color })
      end

      mod[:module].session_types.each do |session_type|
        color = session_type == session.type ? @valid_color : @invalid_color
        session_type_styler.merge!(session_type.to_s => color)
      end

      [
        index + 1,
        mod[:module].fullname,
        mod[:result][:incompatibility_reasons].join('. '),
        platforms.map(&:realname).sort.join(', '),
        arch.any? ? arch.sort.join(', ') : 'No defined architectures',
        mod[:module].session_types.any? ? mod[:module].session_types.sort.join(', ') : 'No defined session types'
      ]
    end

    Rex::Text::Table.new(
      'Header' => "Incompatible modules for session #{session.sid}:",
      'Indent' => 1,
      'Columns' => [ '#', 'Name', 'Reasons', 'Platform', 'Architecture', 'Session Type' ],
      'WordWrap' => false,
      'ColProps' => {
        'Architecture' => {
          'Stylers' => [arch_styler]
        },
        'Platform' => {
          'Stylers' => [platform_styler]
        },
        'Session Type' => {
          'Stylers' => [session_type_styler]
        }
      },
      'Rows' => rows
    )
  end

  def vprint_session_info
    vprint_status 'Current Session Info:'
    vprint_status "Session Type: #{session.type}"
    vprint_status "Architecture: #{session_arch}"
    vprint_status "Platform: #{session.platform}"
  end

  def is_check_interesting?(checkcode)
    [
      Msf::Exploit::CheckCode::Vulnerable,
      Msf::Exploit::CheckCode::Appears,
      Msf::Exploit::CheckCode::Detected
    ].include? checkcode
  end

  def print_status(msg = '')
    super(session ? "#{session.session_host} - #{msg}" : msg)
  end

  def print_good(msg = '')
    super(session ? "#{session.session_host} - #{msg}" : msg)
  end

  def print_error(msg = '')
    super(session ? "#{session.session_host} - #{msg}" : msg)
  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

16 Dec 2024 17:51Current
7.1High risk
Vulners AI Score7.1
66