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/smb_spec.rb
Views: 11766
require 'acceptance_spec_helper'12RSpec.describe 'SMB sessions and SMB modules' do3include_context 'wait_for_expect'45RHOST_REGEX = /\d+\.\d+\.\d+\.\d+:\d+/67tests = {8smb: {9target: {10session_module: "auxiliary/scanner/smb/smb_login",11type: 'SMB',12platforms: [:linux, :osx, :windows],13datastore: {14global: {},15module: {16username: ENV.fetch('SMB_USERNAME', 'acceptance_tests_user'),17password: ENV.fetch('SMB_PASSWORD', 'acceptance_tests_password'),18rhost: ENV.fetch('SMB_RHOST', '127.0.0.1'),19rport: ENV.fetch('SMB_RPORT', '445'),20}21}22},23module_tests: [24{25name: "post/test/smb",26platforms: [:linux, :osx, :windows],27targets: [:session],28skipped: false,29},30{31name: "auxiliary/scanner/smb/smb_lookupsid",32platforms: [:linux, :osx, :windows],33targets: [:session, :rhost],34skipped: false,35lines: {36all: {37required: [38"PIPE(lsarpc) LOCAL",39/User( *)(Administrator|nobody)/,40/Group( *)(None|Domain (Admins|Users|Guests|Computers))/,41],42},43}44},45{46name: "auxiliary/scanner/smb/smb_enumusers",47platforms: [:linux, :osx, :windows],48targets: [:session, :rhost],49skipped: false,50lines: {51all: {52required: [53"acceptance_tests_user",54],55},56}57},58{59name: "auxiliary/scanner/smb/pipe_auditor",60platforms: [:linux, :osx, :windows],61targets: [:session, :rhost],62skipped: false,63lines: {64all: {65required: [66/Pipes: (\\([a-zA-Z]*)(, )?)*/,67],68known_failures: [69/Inaccessible named pipe:/,70/The server responded with an unexpected status code: STATUS_OBJECT_NAME_NOT_FOUND/,71]72},73}74},75{76name: "auxiliary/scanner/smb/smb_enumshares",77platforms: [:linux, :osx, :windows],78targets: [:session, :rhost],79skipped: false,80lines: {81all: {82required: [83"modifiable - (DISK)",84"readonly - (DISK)",85"IPC$ - (IPC|SPECIAL) IPC Service",86],87},88}89},90]91}92}9394allure_test_environment = AllureRspec.configuration.environment_properties9596let_it_be(:current_platform) { Acceptance::Session::current_platform }9798# Driver instance, keeps track of all open processes/payloads/etc, so they can be closed cleanly99let_it_be(:driver) do100driver = Acceptance::ConsoleDriver.new101driver102end103104# Opens a test console with the test loadpath specified105# @!attribute [r] console106# @return [Acceptance::Console]107let_it_be(:console) do108console = driver.open_console109110# Load the test modules111console.sendline('loadpath test/modules')112console.recvuntil(/Loaded \d+ modules:[^\n]*\n/)113console.recvuntil(/\d+ auxiliary modules[^\n]*\n/)114console.recvuntil(/\d+ exploit modules[^\n]*\n/)115console.recvuntil(/\d+ post modules[^\n]*\n/)116console.recvuntil(Acceptance::Console.prompt)117118# Read the remaining console119# console.sendline "quit -y"120# console.recv_available121122features = %w[123smb_session_type124]125126features.each do |feature|127console.sendline("features set #{feature} true")128console.recvuntil(Acceptance::Console.prompt)129end130131console132end133134# Run the given block in a 'test harness' which will handle all of the boilerplate for asserting module results, cleanup, and artifact tracking135# This doesn't happen in a before/after block to ensure that allure's report generation is correctly attached to the correct test scope136def with_test_harness(module_test)137begin138replication_commands = []139140known_failures = module_test.dig(:lines, :all, :known_failures) || []141known_failures += module_test.dig(:lines, current_platform, :known_failures) || []142known_failures = known_failures.flat_map { |value| Acceptance::LineValidation.new(*Array(value)).flatten }143144required_lines = module_test.dig(:lines, :all, :required) || []145required_lines += module_test.dig(:lines, current_platform, :required) || []146required_lines = required_lines.flat_map { |value| Acceptance::LineValidation.new(*Array(value)).flatten }147148yield replication_commands149150# XXX: When debugging failed tests, you can enter into an interactive msfconsole prompt with:151# console.interact152153# Expect the test module to complete154module_type = module_test[:name].split('/').first155test_result = console.recvuntil("#{module_type.capitalize} module execution completed")156157# Ensure there are no failures, and assert tests are complete158aggregate_failures("#{target.type} target and passes the #{module_test[:name].inspect} tests") do159# Skip any ignored lines from the validation input160validated_lines = test_result.lines.reject do |line|161is_acceptable = known_failures.any? do |acceptable_failure|162is_matching_line = acceptable_failure.value.is_a?(Regexp) ? line.match?(acceptable_failure.value) : line.include?(acceptable_failure.value)163is_matching_line &&164acceptable_failure.if?(test_environment)165end || line.match?(/Passed: \d+; Failed: \d+/)166167is_acceptable168end169170validated_lines.each do |test_line|171test_line = Acceptance::Session.uncolorize(test_line)172expect(test_line).to_not include('FAILED', '[-] FAILED', '[-] Exception', '[-] '), "Unexpected error: #{test_line}"173end174175# Assert all expected lines are present176required_lines.each do |required|177next unless required.if?(test_environment)178if required.value.is_a?(Regexp)179expect(test_result).to match(required.value)180else181expect(test_result).to include(required.value)182end183end184185# Assert all ignored lines are present, if they are not present - they should be removed from186# the calling config187known_failures.each do |acceptable_failure|188next if acceptable_failure.flaky?(test_environment)189next unless acceptable_failure.if?(test_environment)190191if acceptable_failure.value.is_a?(Regexp)192expect(test_result).to match(acceptable_failure.value)193else194expect(test_result).to include(acceptable_failure.value)195end196end197end198rescue RSpec::Expectations::ExpectationNotMetError, StandardError => e199test_run_error = e200end201202# Test cleanup. We intentionally omit cleanup from an `after(:each)` to ensure the allure attachments are203# still generated if the session dies in a weird way etc204205console_reset_error = nil206current_console_data = console.all_data207begin208console.reset209rescue => e210console_reset_error = e211Allure.add_attachment(212name: 'console.reset failure information',213source: "Error: #{e.class} - #{e.message}\n#{(e.backtrace || []).join("\n")}",214type: Allure::ContentType::TXT215)216end217218target_configuration_details = target.as_readable_text(219default_global_datastore: default_global_datastore,220default_module_datastore: default_module_datastore221)222223replication_steps = <<~EOF224## Load test modules225loadpath test/modules226227#{target_configuration_details}228229## Replication commands230#{replication_commands.empty? ? 'no additional commands run' : replication_commands.join("\n")}231EOF232233Allure.add_attachment(234name: 'payload configuration and replication',235source: replication_steps,236type: Allure::ContentType::TXT237)238239Allure.add_attachment(240name: 'console data',241source: current_console_data,242type: Allure::ContentType::TXT243)244245test_assertions = JSON.pretty_generate(246{247required_lines: required_lines.map(&:to_h),248known_failures: known_failures.map(&:to_h),249}250)251Allure.add_attachment(252name: 'test assertions',253source: test_assertions,254type: Allure::ContentType::TXT255)256257raise test_run_error if test_run_error258raise console_reset_error if console_reset_error259end260261tests.each do |runtime_name, test_config|262runtime_name = "#{runtime_name}#{ENV.fetch('RUNTIME_VERSION', '')}"263264describe "#{Acceptance::Session.current_platform}/#{runtime_name}", focus: test_config[:focus] do265test_config[:module_tests].each do |module_test|266describe(267module_test[:name],268if: (269Acceptance::Session.supported_platform?(module_test)270)271) do272let(:target) { Acceptance::Target.new(test_config[:target]) }273274let(:default_global_datastore) do275{276}277end278279let(:test_environment) { allure_test_environment }280281let(:default_module_datastore) do282{283lhost: '127.0.0.1'284}285end286287# The shared session id that will be reused across the test run288let(:session_id) do289console.sendline "use #{target.session_module}"290console.recvuntil(Acceptance::Console.prompt)291292# Set global options293console.sendline target.setg_commands(default_global_datastore: default_global_datastore)294console.recvuntil(Acceptance::Console.prompt)295296console.sendline target.run_command(default_module_datastore: { PASS_FILE: nil, USER_FILE: nil, CreateSession: true })297298session_id = nil299# Wait for the session to open, or break early if the payload is detected as dead300wait_for_expect do301session_opened_matcher = /#{target.type} session (\d+) opened[^\n]*\n/302session_message = ''303begin304session_message = console.recvuntil(session_opened_matcher, timeout: 1)305rescue Acceptance::ChildProcessRecvError306# noop307end308309session_id = session_message[session_opened_matcher, 1]310expect(session_id).to_not be_nil311end312313session_id314end315316before :each do |example|317next unless example.respond_to?(:parameter)318319# Add the test environment metadata to the rspec example instance - so it appears in the final allure report UI320test_environment.each do |key, value|321example.parameter(key, value)322end323end324325after :all do326driver.close_payloads327console.reset328end329330context "when targeting a session", if: module_test[:targets].include?(:session) do331it(332"#{Acceptance::Session.current_platform}/#{runtime_name} session opens and passes the #{module_test[:name].inspect} tests"333) do334with_test_harness(module_test) do |replication_commands|335# 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 dies336expect(session_id).to_not(be_nil, proc do337"There should be a session present"338end)339340use_module = "use #{module_test[:name]}"341run_module = "run session=#{session_id} Verbose=true"342343replication_commands << use_module344console.sendline(use_module)345console.recvuntil(Acceptance::Console.prompt)346347replication_commands << run_module348console.sendline(run_module)349350# Assertions will happen after this block ends351end352end353end354355context "when targeting an rhost", if: module_test[:targets].include?(:rhost) do356it(357"#{Acceptance::Session.current_platform}/#{runtime_name} rhost opens and passes the #{module_test[:name].inspect} tests"358) do359with_test_harness(module_test) do |replication_commands|360use_module = "use #{module_test[:name]}"361run_module = "run #{target.datastore_options(default_module_datastore: default_module_datastore)} Verbose=true"362363replication_commands << use_module364console.sendline(use_module)365console.recvuntil(Acceptance::Console.prompt)366367replication_commands << run_module368console.sendline(run_module)369370# Assertions will happen after this block ends371end372end373end374end375end376end377end378end379380381