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/ldap_spec.rb
Views: 11766
require 'acceptance_spec_helper'12RSpec.describe 'LDAP modules' do3include_context 'wait_for_expect'45RHOST_REGEX = /\d+\.\d+\.\d+\.\d+:\d+/67tests = {8ldap: {9target: {10session_module: 'auxiliary/scanner/ldap/ldap_login',11type: 'LDAP',12platforms: %i[linux osx windows],13datastore: {14global: {},15module: {16username: ENV.fetch('LDAP_USERNAME', "'DEV-AD\\Administrator'"),17password: ENV.fetch('LDAP_PASSWORD', 'admin123!'),18rhost: ENV.fetch('LDAP_RHOST', '127.0.0.1'),19rport: ENV.fetch('LDAP_RPORT', '389'),20ssl: ENV.fetch('LDAP_SSL', 'false')21}22}23},24module_tests: [25{26name: 'auxiliary/gather/ldap_query',27platforms: %i[linux osx windows],28targets: [:session, :rhost],29skipped: false,30action: 'run_query_file',31datastore: { QUERY_FILE_PATH: 'data/auxiliary/gather/ldap_query/ldap_queries_default.yaml' },32lines: {33all: {34required: [35/Loading queries from/,36/ldap_queries_default.yaml.../,37/Discovered base DN/,38/Running ENUM_ACCOUNTS.../,39/Running ENUM_USER_SPNS_KERBEROAST.../,40/Running ENUM_USER_PASSWORD_NOT_REQUIRED.../,41]42}43}44},45{46name: 'auxiliary/gather/ldap_query',47platforms: %i[linux osx windows],48targets: [:session, :rhost],49skipped: false,50action: 'enum_accounts',51lines: {52all: {53required: [54/Discovered base DN/,55/Query returned 4 results/56]57}58}59},60{61name: 'auxiliary/gather/ldap_hashdump',62platforms: %i[linux osx windows],63targets: [:session, :rhost],64skipped: false,65lines: {66all: {67required: [68/Searching base DN='DC=ldap,DC=example,DC=com'/,69/Storing LDAP data for base DN='DC=ldap,DC=example,DC=com' in loot/,70/266 entries, 0 creds found in 'DC=ldap,DC=example,DC=com'./71]72}73}74},75{76name: 'auxiliary/admin/ldap/shadow_credentials',77platforms: %i[linux osx windows],78targets: [:session, :rhost],79skipped: false,80datastore: { TARGET_USER: 'administrator' },81lines: {82all: {83required: [84/Discovered base DN: DC=ldap,DC=example,DC=com/,85/The msDS-KeyCredentialLink field is empty./86]87}88}89},90{91name: 'auxiliary/gather/ldap_esc_vulnerable_cert_finder',92platforms: %i[linux osx windows],93targets: [:session, :rhost],94skipped: false,95lines: {96all: {97required: [98/Successfully queried/99]100}101}102},103{104name: 'auxiliary/admin/ldap/rbcd',105platforms: %i[linux osx windows],106targets: [:session, :rhost],107skipped: false,108datastore: { DELEGATE_TO: 'administrator' },109lines: {110all: {111required: [112/The msDS-AllowedToActOnBehalfOfOtherIdentity field is empty./113]114}115}116},117]118}119}120121allure_test_environment = AllureRspec.configuration.environment_properties122123let_it_be(:current_platform) { Acceptance::Session.current_platform }124125# Driver instance, keeps track of all open processes/payloads/etc, so they can be closed cleanly126let_it_be(:driver) do127driver = Acceptance::ConsoleDriver.new128driver129end130131# Opens a test console with the test loadpath specified132# @!attribute [r] console133# @return [Acceptance::Console]134let_it_be(:console) do135console = driver.open_console136137# Load the test modules138console.sendline('loadpath test/modules')139console.recvuntil(/Loaded \d+ modules:[^\n]*\n/)140console.recvuntil(/\d+ auxiliary modules[^\n]*\n/)141console.recvuntil(/\d+ exploit modules[^\n]*\n/)142console.recvuntil(/\d+ post modules[^\n]*\n/)143console.recvuntil(Acceptance::Console.prompt)144145# Read the remaining console146# console.sendline "quit -y"147# console.recv_available148149features = %w[150ldap_session_type151]152153features.each do |feature|154console.sendline("features set #{feature} true")155console.recvuntil(Acceptance::Console.prompt)156end157158console159end160161# Run the given block in a 'test harness' which will handle all of the boilerplate for asserting module results, cleanup, and artifact tracking162# This doesn't happen in a before/after block to ensure that allure's report generation is correctly attached to the correct test scope163def with_test_harness(module_test)164begin165replication_commands = []166167known_failures = module_test.dig(:lines, :all, :known_failures) || []168known_failures += module_test.dig(:lines, current_platform, :known_failures) || []169known_failures = known_failures.flat_map { |value| Acceptance::LineValidation.new(*Array(value)).flatten }170171required_lines = module_test.dig(:lines, :all, :required) || []172required_lines += module_test.dig(:lines, current_platform, :required) || []173required_lines = required_lines.flat_map { |value| Acceptance::LineValidation.new(*Array(value)).flatten }174175yield replication_commands176177# XXX: When debugging failed tests, you can enter into an interactive msfconsole prompt with:178# console.interact179180# Expect the test module to complete181module_type = module_test[:name].split('/').first182test_result = console.recvuntil("#{module_type.capitalize} module execution completed")183184# Ensure there are no failures, and assert tests are complete185aggregate_failures("#{target.type} target and passes the #{module_test[:name].inspect} tests") do186# Skip any ignored lines from the validation input187validated_lines = test_result.lines.reject do |line|188is_acceptable = known_failures.any? do |acceptable_failure|189is_matching_line = is_matching_line.value.is_a?(Regexp) ? line.match?(acceptable_failure.value) : line.include?(acceptable_failure.value)190is_matching_line &&191acceptable_failure.if?(test_environment)192end || line.match?(/Passed: \d+; Failed: \d+/)193194is_acceptable195end196197validated_lines.each do |test_line|198test_line = Acceptance::Session.uncolorize(test_line)199expect(test_line).to_not include('FAILED', '[-] FAILED', '[-] Exception', '[-] '), "Unexpected error: #{test_line}"200end201202# Assert all expected lines are present203required_lines.each do |required|204next unless required.if?(test_environment)205206if required.value.is_a?(Regexp)207expect(test_result).to match(required.value)208else209expect(test_result).to include(required.value)210end211end212213# Assert all ignored lines are present, if they are not present - they should be removed from214# the calling config215known_failures.each do |acceptable_failure|216next if acceptable_failure.flaky?(test_environment)217next unless acceptable_failure.if?(test_environment)218219expect(test_result).to include(acceptable_failure.value)220end221end222rescue RSpec::Expectations::ExpectationNotMetError, StandardError => e223test_run_error = e224end225226# Test cleanup. We intentionally omit cleanup from an `after(:each)` to ensure the allure attachments are227# still generated if the session dies in a weird way etc228229console_reset_error = nil230current_console_data = console.all_data231begin232console.reset233rescue StandardError => e234console_reset_error = e235Allure.add_attachment(236name: 'console.reset failure information',237source: "Error: #{e.class} - #{e.message}\n#{(e.backtrace || []).join("\n")}",238type: Allure::ContentType::TXT239)240end241242target_configuration_details = target.as_readable_text(243default_global_datastore: default_global_datastore,244default_module_datastore: default_module_datastore245)246247replication_steps = <<~EOF248## Load test modules249loadpath test/modules250251#{target_configuration_details}252253## Replication commands254#{replication_commands.empty? ? 'no additional commands run' : replication_commands.join("\n")}255EOF256257Allure.add_attachment(258name: 'payload configuration and replication',259source: replication_steps,260type: Allure::ContentType::TXT261)262263Allure.add_attachment(264name: 'console data',265source: current_console_data,266type: Allure::ContentType::TXT267)268269test_assertions = JSON.pretty_generate(270{271required_lines: required_lines.map(&:to_h),272known_failures: known_failures.map(&:to_h)273}274)275Allure.add_attachment(276name: 'test assertions',277source: test_assertions,278type: Allure::ContentType::TXT279)280281raise test_run_error if test_run_error282raise console_reset_error if console_reset_error283end284285tests.each do |runtime_name, test_config|286runtime_name = "#{runtime_name}#{ENV.fetch('RUNTIME_VERSION', '')}"287288describe "#{Acceptance::Session.current_platform}/#{runtime_name}", focus: test_config[:focus] do289test_config[:module_tests].each do |module_test|290describe(291module_test[:name],292if:293Acceptance::Session.supported_platform?(module_test)294) do295let(:target) { Acceptance::Target.new(test_config[:target]) }296297let(:default_global_datastore) do298{}299end300301let(:test_environment) { allure_test_environment }302303let(:default_module_datastore) do304{305lhost: '127.0.0.1'306}307end308309# The shared session id that will be reused across the test run310let(:session_id) do311console.sendline "use #{target.session_module}"312console.recvuntil(Acceptance::Console.prompt)313314# Set global options315console.sendline target.setg_commands(default_global_datastore: default_global_datastore)316console.recvuntil(Acceptance::Console.prompt)317# TODO: update this when we add sessions318console.sendline target.run_command(default_module_datastore: { PASS_FILE: nil, USER_FILE: nil, CreateSession: true })319320session_id = nil321# Wait for the session to open, or break early if the payload is detected as dead322wait_for_expect do323session_opened_matcher = /#{target.type} session (\d+) opened[^\n]*\n/324session_message = ''325begin326session_message = console.recvuntil(session_opened_matcher, timeout: 1)327rescue Acceptance::ChildProcessRecvError328# noop329end330331session_id = session_message[session_opened_matcher, 1]332expect(session_id).to_not be_nil333end334335session_id336end337338before :each do |example|339next unless example.respond_to?(:parameter)340341# Add the test environment metadata to the rspec example instance - so it appears in the final allure report UI342test_environment.each do |key, value|343example.parameter(key, value)344end345end346347after :all do348driver.close_payloads349console.reset350end351352context 'when targeting a session', if: module_test[:targets].include?(:session) do353it(354"#{Acceptance::Session.current_platform}/#{runtime_name} session opens and passes the #{module_test[:name].inspect} tests"355) do356with_test_harness(module_test) do |replication_commands|357# 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 dies358expect(session_id).to_not(be_nil, proc do359'There should be a session present'360end)361362use_module = "use #{module_test[:name]}"363run_command = module_test.key?(:action) ? module_test.fetch(:action) : 'run'364run_module = "#{run_command} session=#{session_id} #{target.datastore_options(default_module_datastore: default_module_datastore.merge(module_test.fetch(:datastore, {})))} Verbose=true"365366367replication_commands << use_module368console.sendline(use_module)369console.recvuntil(Acceptance::Console.prompt)370371replication_commands << run_module372console.sendline(run_module)373374# Assertions will happen after this block ends375end376end377end378379context 'when targeting an rhost', if: module_test[:targets].include?(:rhost) do380it(381"#{Acceptance::Session.current_platform}/#{runtime_name} rhost opens and passes the #{module_test[:name].inspect} tests"382) do383with_test_harness(module_test) do |replication_commands|384use_module = "use #{module_test[:name]}"385run_command = module_test.key?(:action) ? module_test.fetch(:action) : 'run'386run_module = "#{run_command} #{target.datastore_options(default_module_datastore: default_module_datastore.merge(module_test.fetch(:datastore, {})))} Verbose=true"387388replication_commands << use_module389console.sendline(use_module)390console.recvuntil(Acceptance::Console.prompt)391392replication_commands << run_module393console.sendline(run_module)394395# Assertions will happen after this block ends396end397end398end399end400end401end402end403end404405406