Path: blob/master/modules/post/multi/recon/local_exploit_suggester.rb
19778 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Post67include Msf::Auxiliary::Report89def initialize(info = {})10super(11update_info(12info,13'Name' => 'Multi Recon Local Exploit Suggester',14'Description' => %q{15This module suggests local Metasploit exploits that can be used.1617The exploits are suggested based on the architecture and platform18that the user has a shell opened as well as the available exploits19in meterpreter.2021It's important to note that not all local exploits will be fired.22Exploits are chosen based on these conditions: session type,23platform, architecture, and required default options.24},25'License' => MSF_LICENSE,26'Author' => [ 'sinn3r', 'Mo' ],27'Platform' => all_platforms,28'SessionTypes' => [ 'meterpreter', 'shell' ],29'Notes' => {30'Stability' => [SERVICE_RESOURCE_LOSS],31'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK, CONFIG_CHANGES],32'Reliability' => []33}34)35)36register_options([37Msf::OptInt.new('SESSION', [ true, 'The session to run this module on' ]),38Msf::OptBool.new('SHOWDESCRIPTION', [true, 'Displays a detailed description for the available exploits', false])39])4041register_advanced_options([42Msf::OptBool.new('ValidateArch', [true, 'Validate architecture', true]),43Msf::OptBool.new('ValidatePlatform', [true, 'Validate platform', true]),44Msf::OptBool.new('ValidateMeterpreterCommands', [true, 'Validate Meterpreter commands', false]),45Msf::OptString.new('Colors', [false, 'Valid, Invalid and Ignored colors for module checks (unset to disable)', 'grn/red/blu'])46])47end4849def all_platforms50Msf::Module::Platform.subclasses.collect { |c| c.realname.downcase }51end5253def session_arch54# 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)55session.respond_to?(:native_arch) ? session.native_arch : session.arch56end5758def is_module_arch?(mod)59mod_arch = mod.target.arch || mod.arch60mod_arch.include?(session_arch)61end6263def is_module_wanted?(mod)64mod[:result][:incompatibility_reasons].empty?65end6667def is_session_type?(mod)68# There are some modules that do not define any compatible session types.69# We could assume that means the module can run on all session types,70# Or we could consider that as incorrect module metadata.71mod.session_types.include?(session.type)72end7374def is_module_platform?(mod)75platform_obj = Msf::Module::Platform.find_platform session.platform76return false if mod.target.nil?7778module_platforms = mod.target.platform ? mod.target.platform.platforms : mod.platform.platforms79module_platforms.include? platform_obj80rescue ArgumentError => e81# When not found, find_platform raises an ArgumentError82elog('Could not find a platform', error: e)83return false84end8586def has_required_module_options?(mod)87get_all_missing_module_options(mod).empty?88end8990def get_all_missing_module_options(mod)91missing_options = []92mod.options.each_pair do |option_name, option|93missing_options << option_name if option.required && option.default.nil? && mod.datastore[option_name].blank?94end95missing_options96end9798def valid_incompatibility_reasons(mod, verify_reasons)99# As we can potentially ignore some `reasons` (e.g. accepting arch values which are, on paper, not compatible),100# this keeps track of valid reasons why we will not consider the module that we are evaluating to be valid.101valid_reasons = []102valid_reasons << "Missing required module options (#{get_all_missing_module_options(mod).join('. ')})" unless verify_reasons[:has_required_module_options]103104incompatible_opts = []105incompatible_opts << 'architecture' unless verify_reasons[:is_module_arch]106incompatible_opts << 'platform' unless verify_reasons[:is_module_platform]107incompatible_opts << 'session type' unless verify_reasons[:is_session_type]108valid_reasons << "Not Compatible (#{incompatible_opts.join(', ')})" if incompatible_opts.any?109110valid_reasons << 'Missing/unloadable Meterpreter commands' if verify_reasons[:missing_meterpreter_commands].any?111valid_reasons112end113114def set_module_options(mod)115ignore_list = ['ACTION', 'TARGET'].freeze116datastore.each_pair do |k, v|117mod.datastore[k] = v unless ignore_list.include?(k.upcase)118end119if !mod.datastore['SESSION'] && session.present?120mod.datastore['SESSION'] = session.sid121end122end123124def set_module_target(mod)125session_platform = Msf::Module::Platform.find_platform(session.platform)126target_index = mod.targets.find_index do |target|127# If the target doesn't define its own compatible platforms or architectures, default to the parent (module) values.128target_platforms = target.platform&.platforms || mod.platform.platforms129target_architectures = target.arch || mod.arch130131target_platforms.include?(session_platform) && target_architectures.include?(session_arch)132end133mod.datastore['Target'] = target_index if target_index134end135136def setup137return unless session138139print_status "Collecting local exploits for #{session.session_type}..."140141setup_validation_options142setup_color_options143144# Collects exploits into an array145@local_exploits = []146exploit_refnames = framework.exploits.module_refnames147exploit_refnames.each_with_index do |name, index|148print "%bld%blu[*]%clr Collecting exploit #{index + 1} / #{exploit_refnames.count}\r"149mod = framework.exploits.create name150next unless mod151152set_module_options mod153set_module_target mod154155verify_result = verify_mod(mod)156@local_exploits << { module: mod, result: verify_result } if verify_result[:has_check]157end158end159160def verify_mod(mod)161return { has_check: false } unless mod.is_a?(Msf::Exploit::Local) && mod.has_check?162163result = {164has_check: true,165is_module_platform: (@validate_platform ? is_module_platform?(mod) : true),166is_module_arch: (@validate_arch ? is_module_arch?(mod) : true),167has_required_module_options: has_required_module_options?(mod),168missing_meterpreter_commands: (@validate_meterpreter_commands && session.type == 'meterpreter') ? meterpreter_session_incompatibility_reasons(session) : [],169is_session_type: is_session_type?(mod)170}171result[:incompatibility_reasons] = valid_incompatibility_reasons(mod, result)172result173end174175def setup_validation_options176@validate_arch = datastore['ValidateArch']177@validate_platform = datastore['ValidatePlatform']178@validate_meterpreter_commands = datastore['ValidateMeterpreterCommands']179end180181def setup_color_options182@valid_color, @invalid_color, @ignored_color =183(datastore['Colors'] || '').split('/')184185@valid_color = "%#{@valid_color}" unless @valid_color.blank?186@invalid_color = "%#{@invalid_color}" unless @invalid_color.blank?187@ignored_color = "%#{@ignored_color}" unless @ignored_color.blank?188end189190def show_found_exploits191unless datastore['VERBOSE']192print_status "#{@local_exploits.length} exploit checks are being tried..."193return194end195196vprint_status "The following #{@local_exploits.length} exploit checks are being tried:"197@local_exploits.each do |x|198vprint_status x[:module].fullname199end200end201202def run203runnable_exploits = @local_exploits.select { |mod| is_module_wanted?(mod) }204if runnable_exploits.empty?205print_error 'No suggestions available.'206vprint_line207vprint_session_info208vprint_status unwanted_modules_table(@local_exploits.reject { |mod| is_module_wanted?(mod) })209return210end211212show_found_exploits213results = runnable_exploits.map.with_index do |mod, index|214print "%bld%blu[*]%clr Running check method for exploit #{index + 1} / #{runnable_exploits.count}\r"215begin216checkcode = mod[:module].check217rescue StandardError => e218elog("#Local Exploit Suggester failed with: #{e.class} when using #{mod[:module].shortname}", error: e)219vprint_error "Check with module #{mod[:module].fullname} failed with error #{e.class}"220next { module: mod[:module], errors: ['The check raised an exception.'] }221end222223if checkcode.nil?224vprint_error "Check failed with #{mod[:module].fullname} for unknown reasons"225next { module: mod[:module], errors: ['The check failed for unknown reasons.'] }226end227228# See def is_check_interesting?229unless is_check_interesting? checkcode230vprint_status "#{mod[:module].fullname}: #{checkcode.message}"231next { module: mod[:module], errors: [checkcode.message] }232end233234# Prints the full name and the checkcode message for the exploit235print_good "#{mod[:module].fullname}: #{checkcode.message}"236237# If the datastore option is true, a detailed description will show238if datastore['SHOWDESCRIPTION']239# Formatting for the description text240Rex::Text.wordwrap(Rex::Text.compress(mod[:module].description), 2, 70).split(/\n/).each do |line|241print_line line242end243end244245next { module: mod[:module], checkcode: checkcode.message }246end247248print_line249print_status valid_modules_table(results)250251vprint_line252vprint_session_info253vprint_status unwanted_modules_table(@local_exploits.reject { |mod| is_module_wanted?(mod) })254255report_data = []256results.each do |result|257report_data << [result[:module].fullname, result[:checkcode]] if result[:checkcode]258end259report_note(260host: session.session_host,261type: 'local.suggested_exploits',262data: { :suggested_exploits => report_data }263)264end265266def valid_modules_table(results)267name_styler = ::Msf::Ui::Console::TablePrint::CustomColorStyler.new268check_styler = ::Msf::Ui::Console::TablePrint::CustomColorStyler.new269270# Split all the results by their checkcode.271# We want the modules that returned a checkcode to be at the top.272checkcode_rows, without_checkcode_rows = results.partition { |result| result[:checkcode] }273rows = (checkcode_rows + without_checkcode_rows).map.with_index do |result, index|274color = result[:checkcode] ? @valid_color : @invalid_color275check_res = result.fetch(:checkcode) { result[:errors].join('. ') }276name_styler.merge!({ result[:module].fullname => color })277check_styler.merge!({ check_res => color })278279[280index + 1,281result[:module].fullname,282result[:checkcode] ? 'Yes' : 'No',283check_res284]285end286287Rex::Text::Table.new(288'Header' => "Valid modules for session #{session.sid}:",289'Indent' => 1,290'Columns' => [ '#', 'Name', 'Potentially Vulnerable?', 'Check Result' ],291'SortIndex' => -1,292'WordWrap' => false, # Don't wordwrap as it messes up coloured output when it is broken up into more than one line293'ColProps' => {294'Name' => {295'Stylers' => [name_styler]296},297'Potentially Vulnerable?' => {298'Stylers' => [::Msf::Ui::Console::TablePrint::CustomColorStyler.new({ 'Yes' => @valid_color, 'No' => @invalid_color })]299},300'Check Result' => {301'Stylers' => [check_styler]302}303},304'Rows' => rows305)306end307308def unwanted_modules_table(unwanted_modules)309arch_styler = ::Msf::Ui::Console::TablePrint::CustomColorStyler.new310platform_styler = ::Msf::Ui::Console::TablePrint::CustomColorStyler.new311session_type_styler = ::Msf::Ui::Console::TablePrint::CustomColorStyler.new312313rows = unwanted_modules.map.with_index do |mod, index|314platforms = mod[:module].target.platform&.platforms&.any? ? mod[:module].target.platform.platforms : mod[:module].platform.platforms315platforms ||= []316arch = mod[:module].target.arch&.any? ? mod[:module].target.arch : mod[:module].arch317arch ||= []318319arch.each do |a|320if a != session_arch321if @validate_arch322color = @invalid_color323else324color = @ignored_color325end326else327color = @valid_color328end329330arch_styler.merge!({ a.to_s => color })331end332333platforms.each do |module_platform|334if module_platform != ::Msf::Module::Platform.find_platform(session.platform)335if @validate_platform336color = @invalid_color337else338color = @ignored_color339end340else341color = @valid_color342end343344platform_styler.merge!({ module_platform.realname => color })345end346347mod[:module].session_types.each do |session_type|348color = session_type == session.type ? @valid_color : @invalid_color349session_type_styler.merge!(session_type.to_s => color)350end351352[353index + 1,354mod[:module].fullname,355mod[:result][:incompatibility_reasons].join('. '),356platforms.map(&:realname).sort.join(', '),357arch.any? ? arch.sort.join(', ') : 'No defined architectures',358mod[:module].session_types.any? ? mod[:module].session_types.sort.join(', ') : 'No defined session types'359]360end361362Rex::Text::Table.new(363'Header' => "Incompatible modules for session #{session.sid}:",364'Indent' => 1,365'Columns' => [ '#', 'Name', 'Reasons', 'Platform', 'Architecture', 'Session Type' ],366'WordWrap' => false,367'ColProps' => {368'Architecture' => {369'Stylers' => [arch_styler]370},371'Platform' => {372'Stylers' => [platform_styler]373},374'Session Type' => {375'Stylers' => [session_type_styler]376}377},378'Rows' => rows379)380end381382def vprint_session_info383vprint_status 'Current Session Info:'384vprint_status "Session Type: #{session.type}"385vprint_status "Architecture: #{session_arch}"386vprint_status "Platform: #{session.platform}"387end388389def is_check_interesting?(checkcode)390[391Msf::Exploit::CheckCode::Vulnerable,392Msf::Exploit::CheckCode::Appears,393Msf::Exploit::CheckCode::Detected394].include? checkcode395end396397def print_status(msg = '')398super(session ? "#{session.session_host} - #{msg}" : msg)399end400401def print_good(msg = '')402super(session ? "#{session.session_host} - #{msg}" : msg)403end404405def print_error(msg = '')406super(session ? "#{session.session_host} - #{msg}" : msg)407end408end409410411