Skip to content

Commit 5180efa

Browse files
committed
Update ldap modules to support an ldap session
1 parent f7f759e commit 5180efa

File tree

16 files changed

+318
-180
lines changed

16 files changed

+318
-180
lines changed

lib/msf/core/exploit/remote/ldap.rb

+4-54
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ module Exploit::Remote::LDAP
1212
include Msf::Exploit::Remote::Kerberos::Ticket::Storage
1313
include Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::Options
1414
include Metasploit::Framework::LDAP::Client
15+
include Msf::OptionalSession::LDAP
1516

1617
# Initialize the LDAP client and set up the LDAP specific datastore
1718
# options to allow the client to perform authentication and timeout
@@ -27,8 +28,6 @@ def initialize(info = {})
2728
super
2829

2930
register_options([
30-
Opt::RHOST,
31-
Opt::RPORT(389),
3231
OptBool.new('SSL', [false, 'Enable SSL on the LDAP connection', false]),
3332
Msf::OptString.new('DOMAIN', [false, 'The domain to authenticate to']),
3433
Msf::OptString.new('USERNAME', [false, 'The username to authenticate with'], aliases: ['BIND_DN']),
@@ -121,6 +120,7 @@ def get_connect_opts
121120
# @return [Object] The result of whatever the block that was
122121
# passed in via the "block" parameter yielded.
123122
def ldap_connect(opts = {}, &block)
123+
return yield session.client if session
124124
ldap_open(get_connect_opts.merge(opts), &block)
125125
end
126126

@@ -136,6 +136,7 @@ def ldap_connect(opts = {}, &block)
136136
# @return [Object] The result of whatever the block that was
137137
# passed in via the "block" parameter yielded.
138138
def ldap_open(connect_opts, &block)
139+
return yield session.client if session
139140
opts = resolve_connect_opts(connect_opts)
140141
Rex::Proto::LDAP::Client.open(opts, &block)
141142
end
@@ -160,6 +161,7 @@ def resolve_connect_opts(connect_opts)
160161
# @yieldparam ldap [Rex::Proto::LDAP::Client] The LDAP connection handle to use for connecting to
161162
# the target LDAP server.
162163
def ldap_new(opts = {})
164+
return yield session.client if session
163165

164166
ldap = Rex::Proto::LDAP::Client.new(resolve_connect_opts(get_connect_opts.merge(opts)))
165167

@@ -194,58 +196,6 @@ def ldap.use_connection(args)
194196
yield ldap
195197
end
196198

197-
# # Get the naming contexts for the target LDAP server.
198-
# #
199-
# # @param ldap [Rex::Proto::LDAP::Client] The Rex::Proto::LDAP::Client connection handle for the
200-
# # current LDAP connection.
201-
# # @return [Net::BER::BerIdentifiedArray] Array of naming contexts for the target LDAP server.
202-
# def get_naming_contexts(ldap)
203-
# vprint_status("#{peer} Getting root DSE")
204-
#
205-
# unless (root_dse = ldap.search_root_dse)
206-
# print_error("#{peer} Could not retrieve root DSE")
207-
# return
208-
# end
209-
#
210-
# naming_contexts = root_dse[:namingcontexts]
211-
#
212-
# # NOTE: Rex::Proto::LDAP::Client converts attribute names to lowercase
213-
# if naming_contexts.empty?
214-
# print_error("#{peer} Empty namingContexts attribute")
215-
# return
216-
# end
217-
#
218-
# naming_contexts
219-
# end
220-
221-
# Discover the base DN of the target LDAP server via the LDAP
222-
# server's naming contexts.
223-
#
224-
# @param ldap [Rex::Proto::LDAP::Client] The Rex::Proto::LDAP::Client connection handle for the
225-
# current LDAP connection.
226-
# @return [String] A string containing the base DN of the target LDAP server.
227-
# def discover_base_dn(ldap)
228-
# # @type [Net::BER::BerIdentifiedArray]
229-
# naming_contexts = get_naming_contexts(ldap)
230-
#
231-
# unless naming_contexts
232-
# print_error("#{peer} Base DN cannot be determined")
233-
# return
234-
# end
235-
#
236-
# # NOTE: Find the first entry that starts with `DC=` as this will likely be the base DN.
237-
# naming_contexts.select! { |context| context =~ /^([Dd][Cc]=[A-Za-z0-9-]+,?)+$/ }
238-
# naming_contexts.reject! { |context| context =~ /(Configuration)|(Schema)|(ForestDnsZones)/ }
239-
# if naming_contexts.blank?
240-
# print_error("#{peer} A base DN matching the expected format could not be found!")
241-
# return
242-
# end
243-
# base_dn = naming_contexts[0]
244-
#
245-
# print_good("#{peer} Discovered base DN: #{base_dn}")
246-
# base_dn
247-
# end
248-
249199
# Check whether it was possible to successfully bind to the target LDAP
250200
# server. Raise a RuntimeException with an appropriate error message
251201
# if not.
+9-14
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,15 @@
11
# frozen_string_literal: true
22

3-
module Msf
4-
module Exploit
5-
module Remote
6-
module LDAP
7-
class Error < ::StandardError
3+
module Msf::Exploit::Remote::LDAP
84

9-
attr_reader :error_code
10-
attr_reader :operation_result
11-
def initialize(message: nil, error_code: nil, operation_result: nil)
12-
super(message || 'LDAP Error')
13-
@error_code = error_code
14-
@operation_result = operation_result
15-
end
16-
end
17-
end
5+
class Error < ::StandardError
6+
7+
attr_reader :error_code
8+
attr_reader :operation_result
9+
def initialize(message: nil, error_code: nil, operation_result: nil)
10+
super(message || 'LDAP Error')
11+
@error_code = error_code
12+
@operation_result = operation_result
1813
end
1914
end
2015
end

lib/msf/core/optional_session/ldap.rb

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# frozen_string_literal: true
2+
3+
module Msf
4+
module OptionalSession
5+
module LDAP
6+
include Msf::OptionalSession
7+
8+
RHOST_GROUP_OPTIONS = %w[RHOSTS RPORT DOMAIN USERNAME PASSWORD THREADS]
9+
REQUIRED_OPTIONS = %w[RHOSTS RPORT USERNAME PASSWORD THREADS]
10+
11+
def initialize(info = {})
12+
super(
13+
update_info(
14+
info,
15+
'SessionTypes' => %w[ldap]
16+
)
17+
)
18+
19+
if optional_session_enabled?
20+
register_option_group(name: 'SESSION',
21+
description: 'Used when connecting via an existing SESSION',
22+
option_names: ['SESSION'])
23+
register_option_group(name: 'RHOST',
24+
description: 'Used when making a new connection via RHOSTS',
25+
option_names: RHOST_GROUP_OPTIONS,
26+
required_options: REQUIRED_OPTIONS)
27+
28+
register_options(
29+
[
30+
Msf::OptInt.new('SESSION', [ false, 'The session to run this module on' ]),
31+
Msf::Opt::RHOST(nil, false),
32+
Msf::Opt::RPORT(389, false)
33+
]
34+
)
35+
36+
add_info('New in Metasploit 6.4 - This module can target a %grnSESSION%clr or an %grnRHOST%clr')
37+
else
38+
register_options(
39+
[
40+
Msf::Opt::RHOST,
41+
Msf::Opt::RPORT(389),
42+
]
43+
)
44+
end
45+
end
46+
47+
def optional_session_enabled?
48+
framework.features.enabled?(Msf::FeatureManager::LDAP_SESSION_TYPE)
49+
end
50+
end
51+
end
52+
end

lib/rex/proto/ldap/client.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,10 @@ def _open
7171

7272
def discover_schema_naming_context
7373
result = search(base: '', attributes: [:schemanamingcontext], scope: Net::LDAP::SearchScope_BaseObject)
74-
if result.first && result.first[:schemanamingcontext]
74+
if result.first && !result.first[:schemanamingcontext].empty?
7575
schema_dn = result.first[:schemanamingcontext].first
7676
ilog("#{peerinfo} Discovered Schema DN: #{schema_dn}")
77-
schema_dn
77+
return schema_dn
7878
end
7979
wlog("#{peerinfo} Could not discover Schema DN")
8080
nil

modules/auxiliary/admin/ldap/ad_cs_cert_template.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ def run
109109
else
110110
print_status('Discovering base DN automatically')
111111

112-
unless (@base_dn = discover_base_dn(ldap))
112+
unless (@base_dn = ldap.base_dn)
113113
fail_with(Failure::NotFound, "Couldn't discover base DN!")
114114
end
115115
end

modules/auxiliary/admin/ldap/rbcd.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ def run
138138
else
139139
print_status('Discovering base DN automatically')
140140

141-
unless (@base_dn = discover_base_dn(ldap))
141+
unless (@base_dn = ldap.base_dn)
142142
print_warning("Couldn't discover base DN!")
143143
end
144144
end

modules/auxiliary/admin/ldap/shadow_credentials.rb

+4-2
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55

66
class MetasploitModule < Msf::Auxiliary
77

8-
include Msf::Exploit::Remote::LDAP
98
include Msf::Auxiliary::Report
9+
include Msf::Exploit::Remote::LDAP
1010

1111
ATTRIBUTE = 'msDS-KeyCredentialLink'.freeze
1212

@@ -114,7 +114,9 @@ def run
114114
else
115115
print_status('Discovering base DN automatically')
116116

117-
unless (@base_dn = ldap.base_dn)
117+
if (@base_dn = ldap.base_dn)
118+
print_status("#{ldap.peerinfo} Discovered base DN: #{@base_dn}")
119+
else
118120
print_warning("Couldn't discover base DN!")
119121
end
120122
end

modules/auxiliary/gather/asrep.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ def run_ldap
9999

100100
ldap_connect do |ldap|
101101
validate_bind_success!(ldap)
102-
unless (base_dn = discover_base_dn(ldap))
102+
unless (base_dn = ldap.base_dn)
103103
fail_with(Failure::UnexpectedReply, "Couldn't discover base DN!")
104104
end
105105

modules/auxiliary/gather/ldap_hashdump.rb

+3-3
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55

66
class MetasploitModule < Msf::Auxiliary
77

8-
include Msf::Exploit::Remote::LDAP
98
include Msf::Auxiliary::Scanner
109
include Msf::Auxiliary::Report
10+
include Msf::Exploit::Remote::LDAP
1111

1212
def initialize(info = {})
1313
super(
@@ -33,7 +33,8 @@ def initialize(info = {})
3333
],
3434
'DefaultAction' => 'Dump',
3535
'DefaultOptions' => {
36-
'SSL' => true
36+
'SSL' => true,
37+
'RPORT' => 636
3738
},
3839
'Notes' => {
3940
'Stability' => [CRASH_SAFE],
@@ -44,7 +45,6 @@ def initialize(info = {})
4445
)
4546

4647
register_options([
47-
Opt::RPORT(636), # SSL/TLS
4848
OptInt.new('MAX_LOOT', [false, 'Maximum number of LDAP entries to loot', nil]),
4949
OptInt.new('READ_TIMEOUT', [false, 'LDAP read timeout in seconds', 600]),
5050
OptString.new('BASE_DN', [false, 'LDAP base DN if you already have it']),

modules/auxiliary/gather/vmware_vcenter_vmdir_ldap.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ def run
7777
else
7878
print_status('Discovering base DN automatically')
7979

80-
unless (@base_dn = discover_base_dn(ldap))
80+
unless (@base_dn = ldap.base_dn)
8181
print_warning('Falling back on default base DN dc=vsphere,dc=local')
8282
end
8383
end

modules/auxiliary/scanner/ldap/ldap_login.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ def create_session(result, ip)
169169
# @param [Metasploit::Framework::LoginScanner::Result] result
170170
# @return [Msf::Sessions::LDAP]
171171
def session_setup(result)
172-
return unless (result.connection && result.proof)
172+
return unless result.connection && result.proof
173173

174174
# Create a new session
175175
my_session = Msf::Sessions::LDAP.new(result.connection, { client: result.proof })

spec/acceptance/ldap_spec.rb

+7-9
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
{
2727
name: 'auxiliary/gather/ldap_query',
2828
platforms: %i[linux osx windows],
29-
targets: [:rhost],
29+
targets: [:session, :rhost],
3030
skipped: false,
3131
action: 'run_query_file',
3232
datastore: { QUERY_FILE_PATH: 'data/auxiliary/gather/ldap_query/ldap_queries_default.yaml' },
@@ -39,15 +39,14 @@
3939
/Running ENUM_ACCOUNTS.../,
4040
/Running ENUM_USER_SPNS_KERBEROAST.../,
4141
/Running ENUM_USER_PASSWORD_NOT_REQUIRED.../,
42-
4342
]
4443
}
4544
}
4645
},
4746
{
4847
name: 'auxiliary/gather/ldap_query',
4948
platforms: %i[linux osx windows],
50-
targets: [:rhost],
49+
targets: [:session, :rhost],
5150
skipped: false,
5251
action: 'enum_accounts',
5352
lines: {
@@ -62,13 +61,11 @@
6261
{
6362
name: 'auxiliary/gather/ldap_hashdump',
6463
platforms: %i[linux osx windows],
65-
targets: [:rhost],
64+
targets: [:session, :rhost],
6665
skipped: false,
6766
lines: {
6867
all: {
6968
required: [
70-
/Discovering base DN\(s\) automatically/,
71-
/Dumping data for root DSE/,
7269
/Searching base DN='DC=ldap,DC=example,DC=com'/,
7370
/Storing LDAP data for base DN='DC=ldap,DC=example,DC=com' in loot/,
7471
/266 entries, 0 creds found in 'DC=ldap,DC=example,DC=com'./
@@ -79,13 +76,12 @@
7976
{
8077
name: 'auxiliary/admin/ldap/shadow_credentials',
8178
platforms: %i[linux osx windows],
82-
targets: [:rhost],
79+
targets: [:session, :rhost],
8380
skipped: false,
8481
datastore: { TARGET_USER: 'administrator' },
8582
lines: {
8683
all: {
8784
required: [
88-
/Discovering base DN automatically/,
8985
/Discovered base DN: DC=ldap,DC=example,DC=com/,
9086
/The msDS-KeyCredentialLink field is empty./
9187
]
@@ -338,7 +334,9 @@ def with_test_harness(module_test)
338334
end)
339335

340336
use_module = "use #{module_test[:name]}"
341-
run_module = "run session=#{session_id} Verbose=true"
337+
run_command = module_test.key?(:action) ? module_test.fetch(:action) : 'run'
338+
run_module = "#{run_command} session=#{session_id} #{target.datastore_options(default_module_datastore: default_module_datastore.merge(module_test.fetch(:datastore, {})))} Verbose=true"
339+
342340

343341
replication_commands << use_module
344342
console.sendline(use_module)

0 commit comments

Comments
 (0)