Skip to content

Commit

Permalink
Land rapid7#18050, Add module for running all post test modules
Browse files Browse the repository at this point in the history
  • Loading branch information
dwelch-r7 committed Jun 14, 2023
2 parents 968a151 + 46f7f8e commit 68c48ef
Show file tree
Hide file tree
Showing 12 changed files with 202 additions and 21 deletions.
39 changes: 31 additions & 8 deletions test/lib/module_test.rb
Original file line number Diff line number Diff line change
@@ -1,21 +1,32 @@
require 'rex/stopwatch'

module Msf
module ModuleTest
attr_accessor :tests
attr_accessor :failures
attr_accessor :skipped

class SkipTestError < ::Exception
end

def initialize(info = {})
@tests = 0
@failures = 0
@skipped = 0
super
end

def run_all_tests
tests = self.methods.select { |m| m.to_s =~ /^test_/ }
tests.each { |test_method|
self.send(test_method)
begin
self.send(test_method)
rescue SkipTestError => e
# If the entire def is skipped, increment tests and skip count
@tests += 1
@skipped += 1
print_status("SKIPPED: def #{test_method} (#{e.message})")
end
}
end

Expand All @@ -34,8 +45,10 @@ def it(msg = "", &block)
return
end
rescue SkipTestError => e
@skipped += 1
print_status("SKIPPED: #{msg} (#{e.message})")
rescue ::Exception => e
@failures += 1
print_error("FAILED: #{msg}")
print_error("Exception: #{e.class}: #{e}")
dlog("Exception in testing - #{msg}")
Expand All @@ -49,6 +62,11 @@ def it(msg = "", &block)
def pending(msg = "", &block)
print_status("PENDING: #{msg}")
end

# @return [Integer] The number of tests that have passed
def passed
@tests - @failures
end
end

module ModuleTest::PostTest
Expand All @@ -57,15 +75,20 @@ def run
print_status("Running against session #{datastore["SESSION"]}")
print_status("Session type is #{session.type} and platform is #{session.platform}")

t = Time.now
@tests = 0; @failures = 0
run_all_tests
@tests = 0
@failures = 0
@skipped = 0

_res, elapsed_time = Rex::Stopwatch.elapsed_time do
run_all_tests
end

vprint_status("Testing complete in #{Time.now - t}")
if (@failures > 0)
print_error("Passed: #{@tests - @failures}; Failed: #{@failures}")
vprint_status("Testing complete in #{elapsed_time.round(2)} seconds")
status = "Passed: #{passed}; Failed: #{@failures}; Skipped: #{@skipped}"
if @failures > 0
print_error(status)
else
print_status("Passed: #{@tests - @failures}; Failed: #{@failures}")
print_status(status)
end
end
end
Expand Down
155 changes: 155 additions & 0 deletions test/modules/post/test/all.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
require 'rex'

lib = File.join(Msf::Config.install_root, "test", "lib")
$LOAD_PATH.push(lib) unless $LOAD_PATH.include?(lib)
require 'module_test'

class MetasploitModule < Msf::Post

def initialize(info = {})
super(
update_info(
info,
'Name' => 'Test all applicable post modules',
'Description' => %q{ This module run all applicable post modules against the current session },
'License' => MSF_LICENSE,
'Author' => [ 'alanfoster'],
'Platform' => [ 'linux', 'unix', 'osx', 'windows', 'java' ],
'SessionTypes' => [ 'meterpreter', 'shell', 'powershell' ]
)
)
end

def run
available_modules = select_available_modules
session_metadata = "#{session.session_type} session #{session.sid}"

print_status("Applicable modules:")
print_line(
matching_modules_table(available_modules, header: "Valid modules for #{session_metadata}", with_results: false)
)

module_results = run_modules(available_modules)

print_status("Modules results:")
print_line(matching_modules_table(module_results, header: "Results for #{session_metadata}", with_results: true))
end

def select_available_modules
session_platform = Msf::Module::Platform.find_platform(session.platform)
session_type = session.type

module_results = []
framework.modules.post.each do |refname, _clazz|
next unless refname.start_with?('test/') && refname != self.refname
mod = framework.modules.create(refname)

verify_result = {
is_session_platform: mod.platform.platforms.include?(session_platform),
is_session_type: mod.session_types.include?(session_type)
}
verify_result[:is_valid] = verify_result[:is_session_platform] && verify_result[:is_session_type]
module_results << { module: mod, **verify_result }
end
module_results
end

def run_modules(available_modules)
results = []
available_modules.each do |available_module|
next unless available_module[:is_valid]

print_status("Running #{available_module[:module].refname} against session #{datastore["SESSION"]}")
print_status("-" * 80)

module_replicant = nil
available_module[:module].run_simple(
'LocalInput' => user_input,
'LocalOutput' => user_output,
'Options' => datastore.copy
) { |yielded_module_replicant| module_replicant = yielded_module_replicant }

results << {
**available_module,
tests: module_replicant.tests,
passed: module_replicant.passed,
failures: module_replicant.failures,
skipped: module_replicant.skipped,
}

print_status("-" * 80)
end
results
end

def matching_modules_table(module_results, header:, with_results:)
name_styler = ::Msf::Ui::Console::TablePrint::CustomColorStyler.new
boolean_styler = ::Msf::Ui::Console::TablePrint::CustomColorStyler.new({ 'Yes' => '%grn', 'No' => '%red' })
rows = module_results.sort_by { |module_result| module_result[:is_valid] ? 0 : 1 }.map.with_index do |module_result, index|
next if with_results && !module_result[:is_valid]

name_styler.merge!({ module_result[:module].refname => module_result[:is_valid] ? '%grn' : '%red' })
data = [
index,
module_result[:module].refname,
module_result[:is_session_platform] ? 'Yes' : 'No',
module_result[:is_session_type] ? 'Yes' : 'No',
]

if with_results
data += [
module_result[:tests].to_s,
module_result[:passed].to_s,
module_result[:failures].to_s,
module_result[:skipped].to_s,
]
end

data
end.compact

table = Rex::Text::Table.new(
'Header' => header,
'Indent' => 1,
'Columns' => [ '#', 'Name', 'is_session_platform', 'is_session_type' ] + (with_results ? ['total', 'passed', 'failures', 'skipped'] : []),
'SortIndex' => -1,
'WordWrap' => false,
'ColProps' => {
'Name' => {
'Stylers' => [name_styler]
},
'is_session_platform' => {
'Stylers' => [boolean_styler]
},
'is_session_type' => {
'Stylers' => [boolean_styler]
},
'total' => {
'Stylers' => []
},
'passed' => {
'Stylers' => [StyleIfGreaterThanZero.new(color: '%grn')]
},
'failures' => {
'Stylers' => [StyleIfGreaterThanZero.new(color: '%red')]
},
'skipped' => {
'Stylers' => [StyleIfGreaterThanZero.new(color: '%yel')]
}
},
'Rows' => rows
)

table.to_s
end

class StyleIfGreaterThanZero
def initialize(color:)
@color = color
end

def style(value)
value.to_i > 0 ? "#{@color}#{value}%clr" : value
end
end
end
4 changes: 2 additions & 2 deletions test/modules/post/test/cmd_exec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ def initialize(info = {})
'Name' => 'Meterpreter cmd_exec test',
'Description' => %q( This module will test the meterpreter cmd_exec API ),
'License' => MSF_LICENSE,
'Platform' => ['windows', 'linux', 'unix'],
'SessionTypes' => ['meterpreter']
'Platform' => [ 'windows', 'linux', 'unix', 'java', 'osx' ],
'SessionTypes' => ['meterpreter', 'shell', 'powershell']
)
)
end
Expand Down
4 changes: 2 additions & 2 deletions test/modules/post/test/file.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ def initialize(info = {})
'Description' => %q{ This module will test Post::File API methods },
'License' => MSF_LICENSE,
'Author' => [ 'egypt' ],
'Platform' => [ 'windows', 'linux', 'unix', 'java' ],
'SessionTypes' => [ 'meterpreter', 'shell' ]
'Platform' => [ 'windows', 'linux', 'unix', 'java', 'osx' ],
'SessionTypes' => [ 'meterpreter', 'shell', 'powershell' ]
)
)

Expand Down
4 changes: 2 additions & 2 deletions test/modules/post/test/get_env.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ def initialize(info = {})
'Description' => %q{ This module will test Post::Common get envs API methods },
'License' => MSF_LICENSE,
'Author' => [ 'Ben Campbell'],
'Platform' => [ 'windows', 'linux', 'java', 'python' ],
'SessionTypes' => [ 'meterpreter', 'shell' ]
'Platform' => [ 'windows', 'linux', 'unix', 'java', 'python', 'osx' ],
'SessionTypes' => [ 'meterpreter', 'shell', 'powershell' ]
)
)
end
Expand Down
2 changes: 1 addition & 1 deletion test/modules/post/test/meterpreter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def initialize(info = {})
'Description' => %q{ This module will test meterpreter API methods },
'License' => MSF_LICENSE,
'Author' => [ 'egypt'],
'Platform' => [ 'windows', 'linux', 'java' ],
'Platform' => [ 'windows', 'linux', 'java', 'osx' ],
'SessionTypes' => [ 'meterpreter' ]
)
)
Expand Down
3 changes: 2 additions & 1 deletion test/modules/post/test/railgun.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ def initialize(info = {})
'Description' => %q{ This module will test railgun api functions },
'License' => MSF_LICENSE,
'Author' => [ 'Spencer McIntyre' ],
'Platform' => [ 'linux', 'osx', 'windows' ]
'Platform' => [ 'linux', 'osx', 'windows' ],
'SessionTypes' => [ 'meterpreter' ]
)
)
end
Expand Down
3 changes: 2 additions & 1 deletion test/modules/post/test/railgun_reverse_lookups.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ def initialize(info = {})
'Description' => %q{ This module will test railgun code used in post modules},
'License' => MSF_LICENSE,
'Author' => [ 'kernelsmith'],
'Platform' => [ 'windows' ]
'Platform' => [ 'linux', 'osx', 'windows' ],
'SessionTypes' => [ 'meterpreter' ]
)
)

Expand Down
3 changes: 2 additions & 1 deletion test/modules/post/test/registry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ def initialize(info = {})
'kernelsmith', # original
'egypt', # PostTest conversion
],
'Platform' => [ 'windows' ]
'Platform' => [ 'windows' ],
'SessionTypes' => [ 'meterpreter', 'shell', 'powershell' ]
)
)
end
Expand Down
2 changes: 1 addition & 1 deletion test/modules/post/test/search.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def initialize(info = {})
'Description' => %q{ This module will test the meterpreter search method },
'License' => MSF_LICENSE,
'Author' => [ 'timwr'],
'Platform' => [ 'windows', 'linux', 'java' ],
'Platform' => [ 'windows', 'linux', 'java', 'osx' ],
'SessionTypes' => [ 'meterpreter' ]
)
)
Expand Down
2 changes: 1 addition & 1 deletion test/modules/post/test/services.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def initialize(info = {})
'License' => MSF_LICENSE,
'Author' => [ 'kernelsmith', 'egypt' ],
'Platform' => [ 'windows' ],
'SessionTypes' => [ 'meterpreter', 'shell' ]
'SessionTypes' => [ 'meterpreter', 'shell', 'powershell' ]
)
)
register_options(
Expand Down
2 changes: 1 addition & 1 deletion test/modules/post/test/unix.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def initialize(info = {})
'Description' => %q{ This module will test Post::File API methods },
'License' => MSF_LICENSE,
'Author' => [ 'egypt'],
'Platform' => [ 'linux', 'java' ],
'Platform' => [ 'linux', 'unix', 'java', 'osx' ],
'SessionTypes' => [ 'meterpreter', 'shell' ]
)
)
Expand Down

0 comments on commit 68c48ef

Please sign in to comment.