Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/spec/acceptance/mysql_spec.rb
Views: 11765
require 'acceptance_spec_helper'12RSpec.describe 'MySQL sessions and MySQL modules' do3include_context 'wait_for_expect'45RHOST_REGEX = /\d+\.\d+\.\d+\.\d+:\d+/67tests = {8mysql: {9target: {10session_module: "auxiliary/scanner/mysql/mysql_login",11type: 'MySQL',12platforms: [:linux, :osx, :windows],13datastore: {14global: {},15module: {16username: ENV.fetch('MYSQL_USERNAME', 'root'),17password: ENV.fetch('MYSQL_PASSWORD', 'password'),18rhost: ENV.fetch('MYSQL_RHOST', '127.0.0.1'),19rport: ENV.fetch('MYSQL_RPORT', '3306'),20}21}22},23module_tests: [24{25name: "post/test/mysql",26platforms: [:linux, :osx, :windows],27targets: [:session],28skipped: false,29},30{31name: "auxiliary/scanner/mysql/mysql_hashdump",32platforms: [:linux, :osx, :windows],33targets: [:session, :rhost],34skipped: false,35lines: {36all: {37required: [38/Saving HashString as Loot/39]40},41}42},43{44name: "auxiliary/scanner/mysql/mysql_version",45platforms: [:linux, :osx, :windows],46targets: [:session, :rhost],47skipped: false,48lines: {49all: {50required: [51/#{RHOST_REGEX} is running MySQL \d+.\d+.*/52]53},54}55},56{57name: "auxiliary/admin/mysql/mysql_sql",58platforms: [:linux, :osx, :windows],59targets: [:session, :rhost],60skipped: false,61lines: {62all: {63required: [64/\| \d+.\d+.*/,65]66},67}68},69{70name: "auxiliary/admin/mysql/mysql_enum",71platforms: [:linux, :osx, :windows],72targets: [:session, :rhost],73skipped: false,74lines: {75all: {76required: [77/MySQL Version: \d+.\d+.*/,78]79},80}81},82]83}84}8586allure_test_environment = AllureRspec.configuration.environment_properties8788let_it_be(:current_platform) { Acceptance::Session::current_platform }8990# Driver instance, keeps track of all open processes/payloads/etc, so they can be closed cleanly91let_it_be(:driver) do92driver = Acceptance::ConsoleDriver.new93driver94end9596# Opens a test console with the test loadpath specified97# @!attribute [r] console98# @return [Acceptance::Console]99let_it_be(:console) do100console = driver.open_console101102# Load the test modules103console.sendline('loadpath test/modules')104console.recvuntil(/Loaded \d+ modules:[^\n]*\n/)105console.recvuntil(/\d+ auxiliary modules[^\n]*\n/)106console.recvuntil(/\d+ exploit modules[^\n]*\n/)107console.recvuntil(/\d+ post modules[^\n]*\n/)108console.recvuntil(Acceptance::Console.prompt)109110# Read the remaining console111# console.sendline "quit -y"112# console.recv_available113114features = %w[115mysql_session_type116]117118features.each do |feature|119console.sendline("features set #{feature} true")120console.recvuntil(Acceptance::Console.prompt)121end122123console124end125126# Run the given block in a 'test harness' which will handle all of the boilerplate for asserting module results, cleanup, and artifact tracking127# This doesn't happen in a before/after block to ensure that allure's report generation is correctly attached to the correct test scope128def with_test_harness(module_test)129begin130replication_commands = []131132known_failures = module_test.dig(:lines, :all, :known_failures) || []133known_failures += module_test.dig(:lines, current_platform, :known_failures) || []134known_failures = known_failures.flat_map { |value| Acceptance::LineValidation.new(*Array(value)).flatten }135136required_lines = module_test.dig(:lines, :all, :required) || []137required_lines += module_test.dig(:lines, current_platform, :required) || []138required_lines = required_lines.flat_map { |value| Acceptance::LineValidation.new(*Array(value)).flatten }139140yield replication_commands141142# XXX: When debugging failed tests, you can enter into an interactive msfconsole prompt with:143# console.interact144145# Expect the test module to complete146module_type = module_test[:name].split('/').first147test_result = console.recvuntil("#{module_type.capitalize} module execution completed")148149# Ensure there are no failures, and assert tests are complete150aggregate_failures("#{target.type} target and passes the #{module_test[:name].inspect} tests") do151# Skip any ignored lines from the validation input152validated_lines = test_result.lines.reject do |line|153is_acceptable = known_failures.any? do |acceptable_failure|154is_matching_line = is_matching_line.value.is_a?(Regexp) ? line.match?(acceptable_failure.value) : line.include?(acceptable_failure.value)155is_matching_line &&156acceptable_failure.if?(test_environment)157end || line.match?(/Passed: \d+; Failed: \d+/)158159is_acceptable160end161162validated_lines.each do |test_line|163test_line = Acceptance::Session.uncolorize(test_line)164expect(test_line).to_not include('FAILED', '[-] FAILED', '[-] Exception', '[-] '), "Unexpected error: #{test_line}"165end166167# Assert all expected lines are present168required_lines.each do |required|169next unless required.if?(test_environment)170if required.value.is_a?(Regexp)171expect(test_result).to match(required.value)172else173expect(test_result).to include(required.value)174end175end176177# Assert all ignored lines are present, if they are not present - they should be removed from178# the calling config179known_failures.each do |acceptable_failure|180next if acceptable_failure.flaky?(test_environment)181next unless acceptable_failure.if?(test_environment)182183expect(test_result).to include(acceptable_failure.value)184end185end186rescue RSpec::Expectations::ExpectationNotMetError, StandardError => e187test_run_error = e188end189190# Test cleanup. We intentionally omit cleanup from an `after(:each)` to ensure the allure attachments are191# still generated if the session dies in a weird way etc192193console_reset_error = nil194current_console_data = console.all_data195begin196console.reset197rescue => e198console_reset_error = e199Allure.add_attachment(200name: 'console.reset failure information',201source: "Error: #{e.class} - #{e.message}\n#{(e.backtrace || []).join("\n")}",202type: Allure::ContentType::TXT203)204end205206target_configuration_details = target.as_readable_text(207default_global_datastore: default_global_datastore,208default_module_datastore: default_module_datastore209)210211replication_steps = <<~EOF212## Load test modules213loadpath test/modules214215#{target_configuration_details}216217## Replication commands218#{replication_commands.empty? ? 'no additional commands run' : replication_commands.join("\n")}219EOF220221Allure.add_attachment(222name: 'payload configuration and replication',223source: replication_steps,224type: Allure::ContentType::TXT225)226227Allure.add_attachment(228name: 'console data',229source: current_console_data,230type: Allure::ContentType::TXT231)232233test_assertions = JSON.pretty_generate(234{235required_lines: required_lines.map(&:to_h),236known_failures: known_failures.map(&:to_h),237}238)239Allure.add_attachment(240name: 'test assertions',241source: test_assertions,242type: Allure::ContentType::TXT243)244245raise test_run_error if test_run_error246raise console_reset_error if console_reset_error247end248249tests.each do |runtime_name, test_config|250runtime_name = "#{runtime_name}#{ENV.fetch('RUNTIME_VERSION', '')}"251252describe "#{Acceptance::Session.current_platform}/#{runtime_name}", focus: test_config[:focus] do253test_config[:module_tests].each do |module_test|254describe(255module_test[:name],256if: (257Acceptance::Session.supported_platform?(module_test)258)259) do260let(:target) { Acceptance::Target.new(test_config[:target]) }261262let(:default_global_datastore) do263{264}265end266267let(:test_environment) { allure_test_environment }268269let(:default_module_datastore) do270{271lhost: '127.0.0.1'272}273end274275# The shared session id that will be reused across the test run276let(:session_id) do277console.sendline "use #{target.session_module}"278console.recvuntil(Acceptance::Console.prompt)279280# Set global options281console.sendline target.setg_commands(default_global_datastore: default_global_datastore)282console.recvuntil(Acceptance::Console.prompt)283284console.sendline target.run_command(default_module_datastore: { PASS_FILE: nil, USER_FILE: nil, CreateSession: true })285286session_id = nil287# Wait for the session to open, or break early if the payload is detected as dead288wait_for_expect do289session_opened_matcher = /#{target.type} session (\d+) opened[^\n]*\n/290session_message = ''291begin292session_message = console.recvuntil(session_opened_matcher, timeout: 1)293rescue Acceptance::ChildProcessRecvError294# noop295end296297session_id = session_message[session_opened_matcher, 1]298expect(session_id).to_not be_nil299end300301session_id302end303304before :each do |example|305next unless example.respond_to?(:parameter)306307# Add the test environment metadata to the rspec example instance - so it appears in the final allure report UI308test_environment.each do |key, value|309example.parameter(key, value)310end311end312313after :all do314driver.close_payloads315console.reset316end317318context "when targeting a session", if: module_test[:targets].include?(:session) do319it(320"#{Acceptance::Session.current_platform}/#{runtime_name} session opens and passes the #{module_test[:name].inspect} tests"321) do322with_test_harness(module_test) do |replication_commands|323# 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 dies324expect(session_id).to_not(be_nil, proc do325"There should be a session present"326end)327328use_module = "use #{module_test[:name]}"329run_module = "run session=#{session_id} Verbose=true"330331replication_commands << use_module332console.sendline(use_module)333console.recvuntil(Acceptance::Console.prompt)334335replication_commands << run_module336console.sendline(run_module)337338# Assertions will happen after this block ends339end340end341end342343context "when targeting an rhost", if: module_test[:targets].include?(:rhost) do344it(345"#{Acceptance::Session.current_platform}/#{runtime_name} rhost opens and passes the #{module_test[:name].inspect} tests"346) do347with_test_harness(module_test) do |replication_commands|348use_module = "use #{module_test[:name]}"349run_module = "run #{target.datastore_options(default_module_datastore: default_module_datastore)} Verbose=true"350351replication_commands << use_module352console.sendline(use_module)353console.recvuntil(Acceptance::Console.prompt)354355replication_commands << run_module356console.sendline(run_module)357358# Assertions will happen after this block ends359end360end361end362end363end364end365end366end367368369