Path: blob/master/spec/acceptance/mysql_spec.rb
19848 views
require 'acceptance_spec_helper'12RSpec.describe 'MySQL sessions and MySQL modules' do3include_context 'wait_for_expect'45tests = {6mysql: {7target: {8session_module: "auxiliary/scanner/mysql/mysql_login",9type: 'MySQL',10platforms: [:linux, :osx, :windows],11datastore: {12global: {},13module: {14username: ENV.fetch('MYSQL_USERNAME', 'root'),15password: ENV.fetch('MYSQL_PASSWORD', 'password'),16rhost: ENV.fetch('MYSQL_RHOST', '127.0.0.1'),17rport: ENV.fetch('MYSQL_RPORT', '3306'),18}19}20},21module_tests: [22{23name: "post/test/mysql",24platforms: [:linux, :osx, :windows],25targets: [:session],26skipped: false,27},28{29name: "auxiliary/scanner/mysql/mysql_hashdump",30platforms: [:linux, :osx, :windows],31targets: [:session, :rhost],32skipped: false,33lines: {34all: {35required: [36/Saving HashString as Loot/37]38},39}40},41{42name: "auxiliary/scanner/mysql/mysql_version",43platforms: [:linux, :osx, :windows],44targets: [:session, :rhost],45skipped: false,46lines: {47all: {48required: [49/\d+\.\d+\.\d+\.\d+:\d+ is running MySQL \d+.\d+.*/50]51},52}53},54{55name: "auxiliary/admin/mysql/mysql_sql",56platforms: [:linux, :osx, :windows],57targets: [:session, :rhost],58skipped: false,59lines: {60all: {61required: [62/\| \d+.\d+.*/,63]64},65}66},67{68name: "auxiliary/admin/mysql/mysql_enum",69platforms: [:linux, :osx, :windows],70targets: [:session, :rhost],71skipped: false,72lines: {73all: {74required: [75/MySQL Version: \d+.\d+.*/,76]77},78}79},80]81}82}8384allure_test_environment = AllureRspec.configuration.environment_properties8586let_it_be(:current_platform) { Acceptance::Session::current_platform }8788# Driver instance, keeps track of all open processes/payloads/etc, so they can be closed cleanly89let_it_be(:driver) do90driver = Acceptance::ConsoleDriver.new91driver92end9394# Opens a test console with the test loadpath specified95# @!attribute [r] console96# @return [Acceptance::Console]97let_it_be(:console) do98console = driver.open_console99100# Load the test modules101console.sendline('loadpath test/modules')102console.recvuntil(/Loaded \d+ modules:[^\n]*\n/)103console.recvuntil(/\d+ auxiliary modules[^\n]*\n/)104console.recvuntil(/\d+ exploit modules[^\n]*\n/)105console.recvuntil(/\d+ post modules[^\n]*\n/)106console.recvuntil(Acceptance::Console.prompt)107108# Read the remaining console109# console.sendline "quit -y"110# console.recv_available111112features = %w[113mysql_session_type114]115116features.each do |feature|117console.sendline("features set #{feature} true")118console.recvuntil(Acceptance::Console.prompt)119end120121console122end123124# Run the given block in a 'test harness' which will handle all of the boilerplate for asserting module results, cleanup, and artifact tracking125# This doesn't happen in a before/after block to ensure that allure's report generation is correctly attached to the correct test scope126def with_test_harness(module_test)127begin128replication_commands = []129130known_failures = module_test.dig(:lines, :all, :known_failures) || []131known_failures += module_test.dig(:lines, current_platform, :known_failures) || []132known_failures = known_failures.flat_map { |value| Acceptance::LineValidation.new(*Array(value)).flatten }133134required_lines = module_test.dig(:lines, :all, :required) || []135required_lines += module_test.dig(:lines, current_platform, :required) || []136required_lines = required_lines.flat_map { |value| Acceptance::LineValidation.new(*Array(value)).flatten }137138yield replication_commands139140# XXX: When debugging failed tests, you can enter into an interactive msfconsole prompt with:141# console.interact142143# Expect the test module to complete144module_type = module_test[:name].split('/').first145test_result = console.recvuntil("#{module_type.capitalize} module execution completed")146147# Ensure there are no failures, and assert tests are complete148aggregate_failures("#{target.type} target and passes the #{module_test[:name].inspect} tests") do149# Skip any ignored lines from the validation input150validated_lines = test_result.lines.reject do |line|151is_acceptable = known_failures.any? do |acceptable_failure|152is_matching_line = is_matching_line.value.is_a?(Regexp) ? line.match?(acceptable_failure.value) : line.include?(acceptable_failure.value)153is_matching_line &&154acceptable_failure.if?(test_environment)155end || line.match?(/Passed: \d+; Failed: \d+/)156157is_acceptable158end159160validated_lines.each do |test_line|161test_line = Acceptance::Session.uncolorize(test_line)162expect(test_line).to_not include('FAILED', '[-] FAILED', '[-] Exception', '[-] '), "Unexpected error: #{test_line}"163end164165# Assert all expected lines are present166required_lines.each do |required|167next unless required.if?(test_environment)168if required.value.is_a?(Regexp)169expect(test_result).to match(required.value)170else171expect(test_result).to include(required.value)172end173end174175# Assert all ignored lines are present, if they are not present - they should be removed from176# the calling config177known_failures.each do |acceptable_failure|178next if acceptable_failure.flaky?(test_environment)179next unless acceptable_failure.if?(test_environment)180181expect(test_result).to include(acceptable_failure.value)182end183end184rescue RSpec::Expectations::ExpectationNotMetError, StandardError => e185test_run_error = e186end187188# Test cleanup. We intentionally omit cleanup from an `after(:each)` to ensure the allure attachments are189# still generated if the session dies in a weird way etc190191console_reset_error = nil192current_console_data = console.all_data193begin194console.reset195rescue => e196console_reset_error = e197Allure.add_attachment(198name: 'console.reset failure information',199source: "Error: #{e.class} - #{e.message}\n#{(e.backtrace || []).join("\n")}",200type: Allure::ContentType::TXT201)202end203204target_configuration_details = target.as_readable_text(205default_global_datastore: default_global_datastore,206default_module_datastore: default_module_datastore207)208209replication_steps = <<~EOF210## Load test modules211loadpath test/modules212213#{target_configuration_details}214215## Replication commands216#{replication_commands.empty? ? 'no additional commands run' : replication_commands.join("\n")}217EOF218219Allure.add_attachment(220name: 'payload configuration and replication',221source: replication_steps,222type: Allure::ContentType::TXT223)224225Allure.add_attachment(226name: 'console data',227source: current_console_data,228type: Allure::ContentType::TXT229)230231test_assertions = JSON.pretty_generate(232{233required_lines: required_lines.map(&:to_h),234known_failures: known_failures.map(&:to_h),235}236)237Allure.add_attachment(238name: 'test assertions',239source: test_assertions,240type: Allure::ContentType::TXT241)242243raise test_run_error if test_run_error244raise console_reset_error if console_reset_error245end246247tests.each do |runtime_name, test_config|248runtime_name = "#{runtime_name}#{ENV.fetch('RUNTIME_VERSION', '')}"249250describe "#{Acceptance::Session.current_platform}/#{runtime_name}", focus: test_config[:focus] do251test_config[:module_tests].each do |module_test|252describe(253module_test[:name],254if: (255Acceptance::Session.supported_platform?(module_test)256)257) do258let(:target) { Acceptance::Target.new(test_config[:target]) }259260let(:default_global_datastore) do261{262}263end264265let(:test_environment) { allure_test_environment }266267let(:default_module_datastore) do268{269lhost: '127.0.0.1'270}271end272273# The shared session id that will be reused across the test run274let(:session_id) do275console.sendline "use #{target.session_module}"276console.recvuntil(Acceptance::Console.prompt)277278# Set global options279console.sendline target.setg_commands(default_global_datastore: default_global_datastore)280console.recvuntil(Acceptance::Console.prompt)281282console.sendline target.run_command(default_module_datastore: { PASS_FILE: nil, USER_FILE: nil, CreateSession: true })283284session_id = nil285# Wait for the session to open, or break early if the payload is detected as dead286wait_for_expect do287session_opened_matcher = /#{target.type} session (\d+) opened[^\n]*\n/288session_message = ''289begin290session_message = console.recvuntil(session_opened_matcher, timeout: 1)291rescue Acceptance::ChildProcessRecvError292# noop293end294295session_id = session_message[session_opened_matcher, 1]296expect(session_id).to_not be_nil297end298299session_id300end301302before :each do |example|303next unless example.respond_to?(:parameter)304305# Add the test environment metadata to the rspec example instance - so it appears in the final allure report UI306test_environment.each do |key, value|307example.parameter(key, value)308end309end310311after :all do312driver.close_payloads313console.reset314end315316context "when targeting a session", if: module_test[:targets].include?(:session) do317it(318"#{Acceptance::Session.current_platform}/#{runtime_name} session opens and passes the #{module_test[:name].inspect} tests"319) do320with_test_harness(module_test) do |replication_commands|321# Ensure we have a valid session id; We intentionally omit this from a `before(:each)` to ensure the allure attachments are generated if the session dies322expect(session_id).to_not(be_nil, proc do323"There should be a session present"324end)325326use_module = "use #{module_test[:name]}"327run_module = "run session=#{session_id} Verbose=true"328329replication_commands << use_module330console.sendline(use_module)331console.recvuntil(Acceptance::Console.prompt)332333replication_commands << run_module334console.sendline(run_module)335336# Assertions will happen after this block ends337end338end339end340341context "when targeting an rhost", if: module_test[:targets].include?(:rhost) do342it(343"#{Acceptance::Session.current_platform}/#{runtime_name} rhost opens and passes the #{module_test[:name].inspect} tests"344) do345with_test_harness(module_test) do |replication_commands|346use_module = "use #{module_test[:name]}"347run_module = "run #{target.datastore_options(default_module_datastore: default_module_datastore)} Verbose=true"348349replication_commands << use_module350console.sendline(use_module)351console.recvuntil(Acceptance::Console.prompt)352353replication_commands << run_module354console.sendline(run_module)355356# Assertions will happen after this block ends357end358end359end360end361end362end363end364end365366367