Update ldap modules to support an ldap session
This commit is contained in:
@@ -12,6 +12,7 @@ module Msf
|
|||||||
include Msf::Exploit::Remote::Kerberos::Ticket::Storage
|
include Msf::Exploit::Remote::Kerberos::Ticket::Storage
|
||||||
include Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::Options
|
include Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::Options
|
||||||
include Metasploit::Framework::LDAP::Client
|
include Metasploit::Framework::LDAP::Client
|
||||||
|
include Msf::OptionalSession::LDAP
|
||||||
|
|
||||||
# Initialize the LDAP client and set up the LDAP specific datastore
|
# Initialize the LDAP client and set up the LDAP specific datastore
|
||||||
# options to allow the client to perform authentication and timeout
|
# options to allow the client to perform authentication and timeout
|
||||||
@@ -27,8 +28,6 @@ module Msf
|
|||||||
super
|
super
|
||||||
|
|
||||||
register_options([
|
register_options([
|
||||||
Opt::RHOST,
|
|
||||||
Opt::RPORT(389),
|
|
||||||
OptBool.new('SSL', [false, 'Enable SSL on the LDAP connection', false]),
|
OptBool.new('SSL', [false, 'Enable SSL on the LDAP connection', false]),
|
||||||
Msf::OptString.new('DOMAIN', [false, 'The domain to authenticate to']),
|
Msf::OptString.new('DOMAIN', [false, 'The domain to authenticate to']),
|
||||||
Msf::OptString.new('USERNAME', [false, 'The username to authenticate with'], aliases: ['BIND_DN']),
|
Msf::OptString.new('USERNAME', [false, 'The username to authenticate with'], aliases: ['BIND_DN']),
|
||||||
@@ -122,6 +121,7 @@ module Msf
|
|||||||
# @return [Object] The result of whatever the block that was
|
# @return [Object] The result of whatever the block that was
|
||||||
# passed in via the "block" parameter yielded.
|
# passed in via the "block" parameter yielded.
|
||||||
def ldap_connect(opts = {}, &block)
|
def ldap_connect(opts = {}, &block)
|
||||||
|
return yield session.client if session
|
||||||
ldap_open(get_connect_opts.merge(opts), &block)
|
ldap_open(get_connect_opts.merge(opts), &block)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -137,6 +137,7 @@ module Msf
|
|||||||
# @return [Object] The result of whatever the block that was
|
# @return [Object] The result of whatever the block that was
|
||||||
# passed in via the "block" parameter yielded.
|
# passed in via the "block" parameter yielded.
|
||||||
def ldap_open(connect_opts, &block)
|
def ldap_open(connect_opts, &block)
|
||||||
|
return yield session.client if session
|
||||||
opts = resolve_connect_opts(connect_opts)
|
opts = resolve_connect_opts(connect_opts)
|
||||||
Rex::Proto::LDAP::Client.open(opts, &block)
|
Rex::Proto::LDAP::Client.open(opts, &block)
|
||||||
end
|
end
|
||||||
@@ -161,6 +162,7 @@ module Msf
|
|||||||
# @yieldparam ldap [Rex::Proto::LDAP::Client] The LDAP connection handle to use for connecting to
|
# @yieldparam ldap [Rex::Proto::LDAP::Client] The LDAP connection handle to use for connecting to
|
||||||
# the target LDAP server.
|
# the target LDAP server.
|
||||||
def ldap_new(opts = {})
|
def ldap_new(opts = {})
|
||||||
|
return yield session.client if session
|
||||||
|
|
||||||
ldap = Rex::Proto::LDAP::Client.new(resolve_connect_opts(get_connect_opts.merge(opts)))
|
ldap = Rex::Proto::LDAP::Client.new(resolve_connect_opts(get_connect_opts.merge(opts)))
|
||||||
|
|
||||||
@@ -195,58 +197,6 @@ module Msf
|
|||||||
yield ldap
|
yield ldap
|
||||||
end
|
end
|
||||||
|
|
||||||
# # Get the naming contexts for the target LDAP server.
|
|
||||||
# #
|
|
||||||
# # @param ldap [Rex::Proto::LDAP::Client] The Rex::Proto::LDAP::Client connection handle for the
|
|
||||||
# # current LDAP connection.
|
|
||||||
# # @return [Net::BER::BerIdentifiedArray] Array of naming contexts for the target LDAP server.
|
|
||||||
# def get_naming_contexts(ldap)
|
|
||||||
# vprint_status("#{peer} Getting root DSE")
|
|
||||||
#
|
|
||||||
# unless (root_dse = ldap.search_root_dse)
|
|
||||||
# print_error("#{peer} Could not retrieve root DSE")
|
|
||||||
# return
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# naming_contexts = root_dse[:namingcontexts]
|
|
||||||
#
|
|
||||||
# # NOTE: Rex::Proto::LDAP::Client converts attribute names to lowercase
|
|
||||||
# if naming_contexts.empty?
|
|
||||||
# print_error("#{peer} Empty namingContexts attribute")
|
|
||||||
# return
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# naming_contexts
|
|
||||||
# end
|
|
||||||
|
|
||||||
# Discover the base DN of the target LDAP server via the LDAP
|
|
||||||
# server's naming contexts.
|
|
||||||
#
|
|
||||||
# @param ldap [Rex::Proto::LDAP::Client] The Rex::Proto::LDAP::Client connection handle for the
|
|
||||||
# current LDAP connection.
|
|
||||||
# @return [String] A string containing the base DN of the target LDAP server.
|
|
||||||
# def discover_base_dn(ldap)
|
|
||||||
# # @type [Net::BER::BerIdentifiedArray]
|
|
||||||
# naming_contexts = get_naming_contexts(ldap)
|
|
||||||
#
|
|
||||||
# unless naming_contexts
|
|
||||||
# print_error("#{peer} Base DN cannot be determined")
|
|
||||||
# return
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# # NOTE: Find the first entry that starts with `DC=` as this will likely be the base DN.
|
|
||||||
# naming_contexts.select! { |context| context =~ /^([Dd][Cc]=[A-Za-z0-9-]+,?)+$/ }
|
|
||||||
# naming_contexts.reject! { |context| context =~ /(Configuration)|(Schema)|(ForestDnsZones)/ }
|
|
||||||
# if naming_contexts.blank?
|
|
||||||
# print_error("#{peer} A base DN matching the expected format could not be found!")
|
|
||||||
# return
|
|
||||||
# end
|
|
||||||
# base_dn = naming_contexts[0]
|
|
||||||
#
|
|
||||||
# print_good("#{peer} Discovered base DN: #{base_dn}")
|
|
||||||
# base_dn
|
|
||||||
# end
|
|
||||||
|
|
||||||
# Check whether it was possible to successfully bind to the target LDAP
|
# Check whether it was possible to successfully bind to the target LDAP
|
||||||
# server. Raise a RuntimeException with an appropriate error message
|
# server. Raise a RuntimeException with an appropriate error message
|
||||||
# if not.
|
# if not.
|
||||||
|
|||||||
@@ -1,20 +1,15 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module Msf
|
module Msf::Exploit::Remote::LDAP
|
||||||
module Exploit
|
|
||||||
module Remote
|
|
||||||
module LDAP
|
|
||||||
class Error < ::StandardError
|
|
||||||
|
|
||||||
attr_reader :error_code
|
class Error < ::StandardError
|
||||||
attr_reader :operation_result
|
|
||||||
def initialize(message: nil, error_code: nil, operation_result: nil)
|
attr_reader :error_code
|
||||||
super(message || 'LDAP Error')
|
attr_reader :operation_result
|
||||||
@error_code = error_code
|
def initialize(message: nil, error_code: nil, operation_result: nil)
|
||||||
@operation_result = operation_result
|
super(message || 'LDAP Error')
|
||||||
end
|
@error_code = error_code
|
||||||
end
|
@operation_result = operation_result
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Msf
|
||||||
|
module OptionalSession
|
||||||
|
module LDAP
|
||||||
|
include Msf::OptionalSession
|
||||||
|
|
||||||
|
RHOST_GROUP_OPTIONS = %w[RHOSTS RPORT DOMAIN USERNAME PASSWORD THREADS]
|
||||||
|
REQUIRED_OPTIONS = %w[RHOSTS RPORT USERNAME PASSWORD THREADS]
|
||||||
|
|
||||||
|
def initialize(info = {})
|
||||||
|
super(
|
||||||
|
update_info(
|
||||||
|
info,
|
||||||
|
'SessionTypes' => %w[ldap]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if optional_session_enabled?
|
||||||
|
register_option_group(name: 'SESSION',
|
||||||
|
description: 'Used when connecting via an existing SESSION',
|
||||||
|
option_names: ['SESSION'])
|
||||||
|
register_option_group(name: 'RHOST',
|
||||||
|
description: 'Used when making a new connection via RHOSTS',
|
||||||
|
option_names: RHOST_GROUP_OPTIONS,
|
||||||
|
required_options: REQUIRED_OPTIONS)
|
||||||
|
|
||||||
|
register_options(
|
||||||
|
[
|
||||||
|
Msf::OptInt.new('SESSION', [ false, 'The session to run this module on' ]),
|
||||||
|
Msf::Opt::RHOST(nil, false),
|
||||||
|
Msf::Opt::RPORT(389, false)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
add_info('New in Metasploit 6.4 - This module can target a %grnSESSION%clr or an %grnRHOST%clr')
|
||||||
|
else
|
||||||
|
register_options(
|
||||||
|
[
|
||||||
|
Msf::Opt::RHOST,
|
||||||
|
Msf::Opt::RPORT(389),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def optional_session_enabled?
|
||||||
|
framework.features.enabled?(Msf::FeatureManager::LDAP_SESSION_TYPE)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -71,10 +71,10 @@ module Rex
|
|||||||
|
|
||||||
def discover_schema_naming_context
|
def discover_schema_naming_context
|
||||||
result = search(base: '', attributes: [:schemanamingcontext], scope: Net::LDAP::SearchScope_BaseObject)
|
result = search(base: '', attributes: [:schemanamingcontext], scope: Net::LDAP::SearchScope_BaseObject)
|
||||||
if result.first && result.first[:schemanamingcontext]
|
if result.first && !result.first[:schemanamingcontext].empty?
|
||||||
schema_dn = result.first[:schemanamingcontext].first
|
schema_dn = result.first[:schemanamingcontext].first
|
||||||
ilog("#{peerinfo} Discovered Schema DN: #{schema_dn}")
|
ilog("#{peerinfo} Discovered Schema DN: #{schema_dn}")
|
||||||
schema_dn
|
return schema_dn
|
||||||
end
|
end
|
||||||
wlog("#{peerinfo} Could not discover Schema DN")
|
wlog("#{peerinfo} Could not discover Schema DN")
|
||||||
nil
|
nil
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ class MetasploitModule < Msf::Auxiliary
|
|||||||
else
|
else
|
||||||
print_status('Discovering base DN automatically')
|
print_status('Discovering base DN automatically')
|
||||||
|
|
||||||
unless (@base_dn = discover_base_dn(ldap))
|
unless (@base_dn = ldap.base_dn)
|
||||||
fail_with(Failure::NotFound, "Couldn't discover base DN!")
|
fail_with(Failure::NotFound, "Couldn't discover base DN!")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ class MetasploitModule < Msf::Auxiliary
|
|||||||
else
|
else
|
||||||
print_status('Discovering base DN automatically')
|
print_status('Discovering base DN automatically')
|
||||||
|
|
||||||
unless (@base_dn = discover_base_dn(ldap))
|
unless (@base_dn = ldap.base_dn)
|
||||||
print_warning("Couldn't discover base DN!")
|
print_warning("Couldn't discover base DN!")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
|
|
||||||
class MetasploitModule < Msf::Auxiliary
|
class MetasploitModule < Msf::Auxiliary
|
||||||
|
|
||||||
include Msf::Exploit::Remote::LDAP
|
|
||||||
include Msf::Auxiliary::Report
|
include Msf::Auxiliary::Report
|
||||||
|
include Msf::Exploit::Remote::LDAP
|
||||||
|
|
||||||
ATTRIBUTE = 'msDS-KeyCredentialLink'.freeze
|
ATTRIBUTE = 'msDS-KeyCredentialLink'.freeze
|
||||||
|
|
||||||
@@ -114,7 +114,9 @@ class MetasploitModule < Msf::Auxiliary
|
|||||||
else
|
else
|
||||||
print_status('Discovering base DN automatically')
|
print_status('Discovering base DN automatically')
|
||||||
|
|
||||||
unless (@base_dn = ldap.base_dn)
|
if (@base_dn = ldap.base_dn)
|
||||||
|
print_status("#{ldap.peerinfo} Discovered base DN: #{@base_dn}")
|
||||||
|
else
|
||||||
print_warning("Couldn't discover base DN!")
|
print_warning("Couldn't discover base DN!")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ class MetasploitModule < Msf::Auxiliary
|
|||||||
|
|
||||||
ldap_connect do |ldap|
|
ldap_connect do |ldap|
|
||||||
validate_bind_success!(ldap)
|
validate_bind_success!(ldap)
|
||||||
unless (base_dn = discover_base_dn(ldap))
|
unless (base_dn = ldap.base_dn)
|
||||||
fail_with(Failure::UnexpectedReply, "Couldn't discover base DN!")
|
fail_with(Failure::UnexpectedReply, "Couldn't discover base DN!")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,9 @@
|
|||||||
|
|
||||||
class MetasploitModule < Msf::Auxiliary
|
class MetasploitModule < Msf::Auxiliary
|
||||||
|
|
||||||
include Msf::Exploit::Remote::LDAP
|
|
||||||
include Msf::Auxiliary::Scanner
|
include Msf::Auxiliary::Scanner
|
||||||
include Msf::Auxiliary::Report
|
include Msf::Auxiliary::Report
|
||||||
|
include Msf::Exploit::Remote::LDAP
|
||||||
|
|
||||||
def initialize(info = {})
|
def initialize(info = {})
|
||||||
super(
|
super(
|
||||||
@@ -33,7 +33,8 @@ class MetasploitModule < Msf::Auxiliary
|
|||||||
],
|
],
|
||||||
'DefaultAction' => 'Dump',
|
'DefaultAction' => 'Dump',
|
||||||
'DefaultOptions' => {
|
'DefaultOptions' => {
|
||||||
'SSL' => true
|
'SSL' => true,
|
||||||
|
'RPORT' => 636
|
||||||
},
|
},
|
||||||
'Notes' => {
|
'Notes' => {
|
||||||
'Stability' => [CRASH_SAFE],
|
'Stability' => [CRASH_SAFE],
|
||||||
@@ -44,7 +45,6 @@ class MetasploitModule < Msf::Auxiliary
|
|||||||
)
|
)
|
||||||
|
|
||||||
register_options([
|
register_options([
|
||||||
Opt::RPORT(636), # SSL/TLS
|
|
||||||
OptInt.new('MAX_LOOT', [false, 'Maximum number of LDAP entries to loot', nil]),
|
OptInt.new('MAX_LOOT', [false, 'Maximum number of LDAP entries to loot', nil]),
|
||||||
OptInt.new('READ_TIMEOUT', [false, 'LDAP read timeout in seconds', 600]),
|
OptInt.new('READ_TIMEOUT', [false, 'LDAP read timeout in seconds', 600]),
|
||||||
OptString.new('BASE_DN', [false, 'LDAP base DN if you already have it']),
|
OptString.new('BASE_DN', [false, 'LDAP base DN if you already have it']),
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ class MetasploitModule < Msf::Auxiliary
|
|||||||
else
|
else
|
||||||
print_status('Discovering base DN automatically')
|
print_status('Discovering base DN automatically')
|
||||||
|
|
||||||
unless (@base_dn = discover_base_dn(ldap))
|
unless (@base_dn = ldap.base_dn)
|
||||||
print_warning('Falling back on default base DN dc=vsphere,dc=local')
|
print_warning('Falling back on default base DN dc=vsphere,dc=local')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ class MetasploitModule < Msf::Auxiliary
|
|||||||
# @param [Metasploit::Framework::LoginScanner::Result] result
|
# @param [Metasploit::Framework::LoginScanner::Result] result
|
||||||
# @return [Msf::Sessions::LDAP]
|
# @return [Msf::Sessions::LDAP]
|
||||||
def session_setup(result)
|
def session_setup(result)
|
||||||
return unless (result.connection && result.proof)
|
return unless result.connection && result.proof
|
||||||
|
|
||||||
# Create a new session
|
# Create a new session
|
||||||
my_session = Msf::Sessions::LDAP.new(result.connection, { client: result.proof })
|
my_session = Msf::Sessions::LDAP.new(result.connection, { client: result.proof })
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ RSpec.describe 'LDAP modules' do
|
|||||||
{
|
{
|
||||||
name: 'auxiliary/gather/ldap_query',
|
name: 'auxiliary/gather/ldap_query',
|
||||||
platforms: %i[linux osx windows],
|
platforms: %i[linux osx windows],
|
||||||
targets: [:rhost],
|
targets: [:session, :rhost],
|
||||||
skipped: false,
|
skipped: false,
|
||||||
action: 'run_query_file',
|
action: 'run_query_file',
|
||||||
datastore: { QUERY_FILE_PATH: 'data/auxiliary/gather/ldap_query/ldap_queries_default.yaml' },
|
datastore: { QUERY_FILE_PATH: 'data/auxiliary/gather/ldap_query/ldap_queries_default.yaml' },
|
||||||
@@ -39,7 +39,6 @@ RSpec.describe 'LDAP modules' do
|
|||||||
/Running ENUM_ACCOUNTS.../,
|
/Running ENUM_ACCOUNTS.../,
|
||||||
/Running ENUM_USER_SPNS_KERBEROAST.../,
|
/Running ENUM_USER_SPNS_KERBEROAST.../,
|
||||||
/Running ENUM_USER_PASSWORD_NOT_REQUIRED.../,
|
/Running ENUM_USER_PASSWORD_NOT_REQUIRED.../,
|
||||||
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -47,7 +46,7 @@ RSpec.describe 'LDAP modules' do
|
|||||||
{
|
{
|
||||||
name: 'auxiliary/gather/ldap_query',
|
name: 'auxiliary/gather/ldap_query',
|
||||||
platforms: %i[linux osx windows],
|
platforms: %i[linux osx windows],
|
||||||
targets: [:rhost],
|
targets: [:session, :rhost],
|
||||||
skipped: false,
|
skipped: false,
|
||||||
action: 'enum_accounts',
|
action: 'enum_accounts',
|
||||||
lines: {
|
lines: {
|
||||||
@@ -62,13 +61,11 @@ RSpec.describe 'LDAP modules' do
|
|||||||
{
|
{
|
||||||
name: 'auxiliary/gather/ldap_hashdump',
|
name: 'auxiliary/gather/ldap_hashdump',
|
||||||
platforms: %i[linux osx windows],
|
platforms: %i[linux osx windows],
|
||||||
targets: [:rhost],
|
targets: [:session, :rhost],
|
||||||
skipped: false,
|
skipped: false,
|
||||||
lines: {
|
lines: {
|
||||||
all: {
|
all: {
|
||||||
required: [
|
required: [
|
||||||
/Discovering base DN\(s\) automatically/,
|
|
||||||
/Dumping data for root DSE/,
|
|
||||||
/Searching base DN='DC=ldap,DC=example,DC=com'/,
|
/Searching base DN='DC=ldap,DC=example,DC=com'/,
|
||||||
/Storing LDAP data for base DN='DC=ldap,DC=example,DC=com' in loot/,
|
/Storing LDAP data for base DN='DC=ldap,DC=example,DC=com' in loot/,
|
||||||
/266 entries, 0 creds found in 'DC=ldap,DC=example,DC=com'./
|
/266 entries, 0 creds found in 'DC=ldap,DC=example,DC=com'./
|
||||||
@@ -79,13 +76,12 @@ RSpec.describe 'LDAP modules' do
|
|||||||
{
|
{
|
||||||
name: 'auxiliary/admin/ldap/shadow_credentials',
|
name: 'auxiliary/admin/ldap/shadow_credentials',
|
||||||
platforms: %i[linux osx windows],
|
platforms: %i[linux osx windows],
|
||||||
targets: [:rhost],
|
targets: [:session, :rhost],
|
||||||
skipped: false,
|
skipped: false,
|
||||||
datastore: { TARGET_USER: 'administrator' },
|
datastore: { TARGET_USER: 'administrator' },
|
||||||
lines: {
|
lines: {
|
||||||
all: {
|
all: {
|
||||||
required: [
|
required: [
|
||||||
/Discovering base DN automatically/,
|
|
||||||
/Discovered base DN: DC=ldap,DC=example,DC=com/,
|
/Discovered base DN: DC=ldap,DC=example,DC=com/,
|
||||||
/The msDS-KeyCredentialLink field is empty./
|
/The msDS-KeyCredentialLink field is empty./
|
||||||
]
|
]
|
||||||
@@ -338,7 +334,9 @@ RSpec.describe 'LDAP modules' do
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
use_module = "use #{module_test[:name]}"
|
use_module = "use #{module_test[:name]}"
|
||||||
run_module = "run session=#{session_id} Verbose=true"
|
run_command = module_test.key?(:action) ? module_test.fetch(:action) : 'run'
|
||||||
|
run_module = "#{run_command} session=#{session_id} #{target.datastore_options(default_module_datastore: default_module_datastore.merge(module_test.fetch(:datastore, {})))} Verbose=true"
|
||||||
|
|
||||||
|
|
||||||
replication_commands << use_module
|
replication_commands << use_module
|
||||||
console.sendline(use_module)
|
console.sendline(use_module)
|
||||||
|
|||||||
@@ -3,15 +3,19 @@
|
|||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe Msf::Exploit::Remote::LDAP do
|
RSpec.describe Msf::Exploit::Remote::LDAP do
|
||||||
|
include_context 'Msf::Simple::Framework'
|
||||||
|
|
||||||
subject do
|
subject do
|
||||||
mod = ::Msf::Exploit.new
|
mod = ::Msf::Exploit.new
|
||||||
mod.extend described_class
|
mod.extend described_class
|
||||||
|
|
||||||
mod.send(:initialize)
|
|
||||||
mod
|
mod
|
||||||
end
|
end
|
||||||
|
|
||||||
|
before(:each) do
|
||||||
|
allow(subject).to receive(:framework).and_return(framework)
|
||||||
|
end
|
||||||
|
|
||||||
let(:rhost) do
|
let(:rhost) do
|
||||||
'rhost.example.com'
|
'rhost.example.com'
|
||||||
end
|
end
|
||||||
@@ -115,90 +119,90 @@ RSpec.describe Msf::Exploit::Remote::LDAP do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#get_naming_contexts' do
|
# describe '#get_naming_contexts' do
|
||||||
let(:ldap) do
|
# let(:ldap) do
|
||||||
instance_double(Net::LDAP)
|
# instance_double(Net::LDAP)
|
||||||
end
|
# end
|
||||||
context 'Could not retrieve root DSE' do
|
# context 'Could not retrieve root DSE' do
|
||||||
it do
|
# it do
|
||||||
expect(ldap).to receive(:search_root_dse).and_return(false)
|
# expect(ldap).to receive(:search_root_dse).and_return(false)
|
||||||
expect(subject.get_naming_contexts(ldap)).to be(nil)
|
# expect(subject.get_naming_contexts(ldap)).to be(nil)
|
||||||
end
|
# end
|
||||||
end
|
# end
|
||||||
|
|
||||||
context 'Empty naming contexts' do
|
# context 'Empty naming contexts' do
|
||||||
let(:root_dse) do
|
# let(:root_dse) do
|
||||||
{ namingcontexts: [] }
|
# { namingcontexts: [] }
|
||||||
end
|
# end
|
||||||
it do
|
# it do
|
||||||
expect(ldap).to receive(:search_root_dse).and_return(root_dse)
|
# expect(ldap).to receive(:search_root_dse).and_return(root_dse)
|
||||||
expect(subject.get_naming_contexts(ldap)).to be(nil)
|
# expect(subject.get_naming_contexts(ldap)).to be(nil)
|
||||||
end
|
# end
|
||||||
end
|
# end
|
||||||
|
|
||||||
context 'Naming contexts are present' do
|
# context 'Naming contexts are present' do
|
||||||
|
#
|
||||||
|
# let(:naming_contexts) {
|
||||||
|
# %w[context1 context2]
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# let(:root_dse) do
|
||||||
|
# { namingcontexts: naming_contexts }
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# it do
|
||||||
|
# expect(ldap).to receive(:search_root_dse).and_return(root_dse)
|
||||||
|
# expect(subject.get_naming_contexts(ldap)).to be(naming_contexts)
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
|
||||||
let(:naming_contexts) {
|
# describe '#discover_base_dn' do
|
||||||
%w[context1 context2]
|
# let(:ldap) do
|
||||||
}
|
# instance_double(Net::LDAP)
|
||||||
|
# end
|
||||||
let(:root_dse) do
|
#
|
||||||
{ namingcontexts: naming_contexts }
|
# context 'No naming contexts' do
|
||||||
end
|
# it do
|
||||||
|
# expect(subject).to receive(:get_naming_contexts).with(ldap).and_return(nil)
|
||||||
it do
|
# expect(subject.discover_base_dn(ldap)).to be(nil)
|
||||||
expect(ldap).to receive(:search_root_dse).and_return(root_dse)
|
# end
|
||||||
expect(subject.get_naming_contexts(ldap)).to be(naming_contexts)
|
# end
|
||||||
end
|
#
|
||||||
end
|
# context 'Invalid naming contexts' do
|
||||||
end
|
# let(:invalid_naming_contexts) do
|
||||||
|
# %w[invalid1 invalid2]
|
||||||
describe '#discover_base_dn' do
|
# end
|
||||||
let(:ldap) do
|
# it do
|
||||||
instance_double(Net::LDAP)
|
# expect(subject).to receive(:get_naming_contexts).with(ldap).and_return(invalid_naming_contexts)
|
||||||
end
|
# expect(subject.discover_base_dn(ldap)).to be(nil)
|
||||||
|
# end
|
||||||
context 'No naming contexts' do
|
# end
|
||||||
it do
|
#
|
||||||
expect(subject).to receive(:get_naming_contexts).with(ldap).and_return(nil)
|
# context 'Valid naming contexts' do
|
||||||
expect(subject.discover_base_dn(ldap)).to be(nil)
|
# let(:base_dn) do
|
||||||
end
|
# 'DC=abcdef'
|
||||||
end
|
# end
|
||||||
|
# let(:valid_naming_contexts) do
|
||||||
context 'Invalid naming contexts' do
|
# [base_dn]
|
||||||
let(:invalid_naming_contexts) do
|
# end
|
||||||
%w[invalid1 invalid2]
|
# it do
|
||||||
end
|
# expect(subject).to receive(:get_naming_contexts).with(ldap).and_return(valid_naming_contexts)
|
||||||
it do
|
# expect(subject.discover_base_dn(ldap)).to be(base_dn)
|
||||||
expect(subject).to receive(:get_naming_contexts).with(ldap).and_return(invalid_naming_contexts)
|
# end
|
||||||
expect(subject.discover_base_dn(ldap)).to be(nil)
|
# end
|
||||||
end
|
#
|
||||||
end
|
# context 'Valid naming contexts (lowercase dc)' do
|
||||||
|
# let(:base_dn) do
|
||||||
context 'Valid naming contexts' do
|
# 'dc=abcdef'
|
||||||
let(:base_dn) do
|
# end
|
||||||
'DC=abcdef'
|
# let(:valid_naming_contexts) do
|
||||||
end
|
# [base_dn]
|
||||||
let(:valid_naming_contexts) do
|
# end
|
||||||
[base_dn]
|
# it do
|
||||||
end
|
# expect(subject).to receive(:get_naming_contexts).with(ldap).and_return(valid_naming_contexts)
|
||||||
it do
|
# expect(subject.discover_base_dn(ldap)).to be(base_dn)
|
||||||
expect(subject).to receive(:get_naming_contexts).with(ldap).and_return(valid_naming_contexts)
|
# end
|
||||||
expect(subject.discover_base_dn(ldap)).to be(base_dn)
|
# end
|
||||||
end
|
# end
|
||||||
end
|
|
||||||
|
|
||||||
context 'Valid naming contexts (lowercase dc)' do
|
|
||||||
let(:base_dn) do
|
|
||||||
'dc=abcdef'
|
|
||||||
end
|
|
||||||
let(:valid_naming_contexts) do
|
|
||||||
[base_dn]
|
|
||||||
end
|
|
||||||
it do
|
|
||||||
expect(subject).to receive(:get_naming_contexts).with(ldap).and_return(valid_naming_contexts)
|
|
||||||
expect(subject.discover_base_dn(ldap)).to be(base_dn)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -14,4 +14,145 @@ RSpec.describe Rex::Proto::LDAP::Client do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'session compatible client'
|
it_behaves_like 'session compatible client'
|
||||||
|
|
||||||
|
let(:base_dn) { 'DC=ldap,DC=example,DC=com' }
|
||||||
|
let(:schema_dn) { 'CN=Schema,CN=Configuration,DC=ldap,DC=example,DC=com' }
|
||||||
|
|
||||||
|
let(:root_dse_result_ldif) do
|
||||||
|
"dn: \n" \
|
||||||
|
"namingcontexts: #{base_dn}\n" \
|
||||||
|
"namingcontexts: CN=Configuration,DC=ldap,DC=example,DC=com\n" \
|
||||||
|
"namingcontexts: CN=Schema,CN=Configuration,DC=ldap,DC=example,DC=com\n" \
|
||||||
|
"namingcontexts: DC=DomainDnsZones,DC=ldap,DC=example,DC=com\n" \
|
||||||
|
"namingcontexts: DC=ForestDnsZones,DC=ldap,DC=example,DC=com\n" \
|
||||||
|
"supportedldapversion: 2\n" \
|
||||||
|
"supportedldapversion: 3\n" \
|
||||||
|
"supportedsaslmechanisms: GSS-SPNEGO\n" \
|
||||||
|
"supportedsaslmechanisms: GSSAPI\n" \
|
||||||
|
"supportedsaslmechanisms: NTLM\n"
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:schema_naming_context) do
|
||||||
|
"dn: \n" \
|
||||||
|
"schemanamingcontext: #{schema_dn}\n"
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:empty_response) do
|
||||||
|
"dn: \n"
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:schema_naming_context_result) do
|
||||||
|
root_dse_dataset = Net::LDAP::Dataset.read_ldif(StringIO.new(schema_naming_context))
|
||||||
|
root_dse_dataset.to_entries
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:root_dse_result) do
|
||||||
|
root_dse_dataset = Net::LDAP::Dataset.read_ldif(StringIO.new(root_dse_result_ldif))
|
||||||
|
root_dse_dataset.to_entries[0]
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:empty_response_result) do
|
||||||
|
root_dse_dataset = Net::LDAP::Dataset.read_ldif(StringIO.new(empty_response))
|
||||||
|
root_dse_dataset.to_entries
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#naming_contexts' do
|
||||||
|
|
||||||
|
before(:each) do
|
||||||
|
allow(subject).to receive(:search_root_dse).and_return(root_dse_result)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should cache the result' do
|
||||||
|
expect(subject).to receive(:search_root_dse)
|
||||||
|
subject.naming_contexts
|
||||||
|
expect(subject).not_to receive(:search_root_dse)
|
||||||
|
subject.naming_contexts
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when no naming contexts are available' do
|
||||||
|
let(:root_dse_result_ldif) do
|
||||||
|
"dn: \n" \
|
||||||
|
"supportedldapversion: 2\n" \
|
||||||
|
"supportedldapversion: 3\n" \
|
||||||
|
"supportedsaslmechanisms: GSS-SPNEGO\n" \
|
||||||
|
"supportedsaslmechanisms: GSSAPI\n" \
|
||||||
|
"supportedsaslmechanisms: NTLM\n"
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns an empty array' do
|
||||||
|
expect(subject.naming_contexts).to be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when naming contexts are available' do
|
||||||
|
it 'contains naming contexts' do
|
||||||
|
expect(subject.naming_contexts).not_to be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#base_dn' do
|
||||||
|
|
||||||
|
before(:each) do
|
||||||
|
allow(subject).to receive(:search_root_dse).and_return(root_dse_result)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should cache the result' do
|
||||||
|
expect(subject).to receive(:discover_base_dn).and_call_original
|
||||||
|
subject.base_dn
|
||||||
|
expect(subject).not_to receive(:discover_base_dn)
|
||||||
|
subject.base_dn
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when no naming contexts are available' do
|
||||||
|
let(:root_dse_result_ldif) do
|
||||||
|
"dn: \n" \
|
||||||
|
"supportedldapversion: 2\n" \
|
||||||
|
"supportedldapversion: 3\n" \
|
||||||
|
"supportedsaslmechanisms: GSS-SPNEGO\n" \
|
||||||
|
"supportedsaslmechanisms: GSSAPI\n" \
|
||||||
|
"supportedsaslmechanisms: NTLM\n"
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should not find the base dn' do
|
||||||
|
expect(subject.base_dn).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when naming contexts are available' do
|
||||||
|
it 'contains naming contexts' do
|
||||||
|
expect(subject.base_dn).to eql(base_dn)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#schema_dn' do
|
||||||
|
|
||||||
|
before(:each) do
|
||||||
|
allow(subject).to receive(:search).and_return(schema_naming_context_result)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should cache the result' do
|
||||||
|
expect(subject).to receive(:discover_schema_naming_context).and_call_original
|
||||||
|
subject.schema_dn
|
||||||
|
expect(subject).not_to receive(:discover_schema_naming_context)
|
||||||
|
subject.schema_dn
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the response does not contain the schema_dn' do
|
||||||
|
before(:each) do
|
||||||
|
allow(subject).to receive(:search).and_return(empty_response_result)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not find the schema_dn' do
|
||||||
|
expect(subject.schema_dn).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the response does contain the schema_dn' do
|
||||||
|
it 'finds the schema_dn' do
|
||||||
|
expect(subject.schema_dn).to eql(schema_dn)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
version: '3.7'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
ldap:
|
ldap:
|
||||||
tty: true
|
tty: true
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
version: '3.7'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
samba:
|
samba:
|
||||||
tty: true
|
tty: true
|
||||||
|
|||||||
Reference in New Issue
Block a user