##
# 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