Skip to content

Commit e348489

Browse files
committed
Add and use a generic LDAP entry cache
1 parent d8eff2a commit e348489

File tree

4 files changed

+56
-12
lines changed

4 files changed

+56
-12
lines changed

Gemfile.lock

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ PATH
4141
irb (~> 1.7.4)
4242
jsobfu
4343
json
44+
lru_redux
4445
metasm
4546
metasploit-concern
4647
metasploit-credential
@@ -303,6 +304,7 @@ GEM
303304
loofah (2.24.0)
304305
crass (~> 1.0.2)
305306
nokogiri (>= 1.12.0)
307+
lru_redux (1.1.0)
306308
memory_profiler (1.1.0)
307309
metasm (1.0.5)
308310
metasploit-concern (5.0.4)

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

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ module Msf
77
module Exploit::Remote::LDAP
88
module ActiveDirectory
99
include Msf::Exploit::Remote::LDAP
10+
include Msf::Exploit::Remote::LDAP::EntryCache
1011

1112
LDAP_CAP_ACTIVE_DIRECTORY_OID = '1.2.840.113556.1.4.800'.freeze
1213
LDAP_SERVER_SD_FLAGS_OID = '1.2.840.113556.1.4.801'.freeze
@@ -174,41 +175,38 @@ def adds_query_member_groups(ldap, member_dn, base_dn: nil, inherited: true)
174175
end
175176

176177
def adds_get_object_by_dn(ldap, object_dn)
177-
@ldap_objects ||= []
178-
object = @ldap_objects.find { |o| o[:dN]&.first == object_dn }
178+
object = ldap_entry_cache.get_by_dn(object_dn)
179179
return object if object
180180

181181
object = ldap.search(base: object_dn, controls: [adds_build_ldap_sd_control], scope: Net::LDAP::SearchScope_BaseObject)&.first
182182
validate_query_result!(ldap.get_operation_result.table)
183183

184-
@ldap_objects << object if object
184+
ldap_entry_cache << object if object
185185
object
186186
end
187187

188188
def adds_get_object_by_samaccountname(ldap, object_samaccountname)
189-
@ldap_objects ||= []
190-
object = @ldap_objects.find { |o| o[:sAMAccountName]&.first == object_samaccountname }
189+
object = ldap_entry_cache.get_by_samaccountname(object_samaccountname)
191190
return object if object
192191

193192
filter = "(sAMAccountName=#{ldap_escape_filter(object_samaccountname)})"
194193
object = ldap.search(base: ldap.base_dn, controls: [adds_build_ldap_sd_control], filter: filter)&.first
195194
validate_query_result!(ldap.get_operation_result.table, filter)
196195

197-
@ldap_objects << object if object
196+
ldap_entry_cache << object if object
198197
object
199198
end
200199

201200
def adds_get_object_by_sid(ldap, object_sid)
202-
@ldap_objects ||= []
203201
object_sid = Rex::Proto::MsDtyp::MsDtypSid.new(object_sid)
204-
object = @ldap_objects.find { |o| o[:objectSid]&.first == object_sid.to_binary_s }
202+
object = ldap_entry_cache.get_by_sid(object_sid)
205203
return object if object
206204

207205
filter = "(objectSID=#{ldap_escape_filter(object_sid.to_s)})"
208206
object = ldap.search(base: ldap.base_dn, controls: [adds_build_ldap_sd_control], filter: filter)&.first
209207
validate_query_result!(ldap.get_operation_result.table, filter)
210208

211-
@ldap_objects << object if object
209+
ldap_entry_cache << object if object
212210
object
213211
end
214212

@@ -228,11 +226,10 @@ def adds_get_current_user(ldap)
228226
# @param [Net::LDAP::Connection] ldap The LDAP connection to use for querying.
229227
# @rtype [Hash]
230228
def adds_get_domain_info(ldap)
231-
@ldap_objects ||= []
232229
domain_object = ldap.search(base: ldap.base_dn, filter: '(objectClass=domain)', return_result: true)&.first
233230
return nil unless domain_object
234231

235-
@ldap_objects << domain_object
232+
ldap_entry_cache << domain_object
236233
domain_sid = Rex::Proto::MsDtyp::MsDtypSid.read(domain_object[:objectSid].first)
237234

238235
root_dse = ldap.search(
@@ -249,7 +246,7 @@ def adds_get_domain_info(ldap)
249246
return nil unless xrefs&.length == 1
250247

251248
xref = xrefs.first
252-
@ldap_objects << xref
249+
ldap_entry_cache << xref
253250

254251
{
255252
netbios_name: xref[:nETBIOSName].first.to_s,
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
require 'lru_redux'
2+
3+
module Msf
4+
###
5+
#
6+
# This module exposes methods for querying a remote LDAP service
7+
#
8+
###
9+
module Exploit::Remote::LDAP
10+
module EntryCache
11+
class LDAPEntryCache < LruRedux::Cache
12+
def <<(entry)
13+
raise TypeError unless entry.is_a? Net::LDAP::Entry
14+
15+
self[entry.dn] = entry
16+
end
17+
18+
def get_by_dn(dn)
19+
self[dn]
20+
end
21+
22+
def get_by_samaccountname(samaccountname)
23+
entry = @data.values.reverse_each.find { _1[:sAMAccountName]&.first == samaccountname }
24+
@data[entry.dn] = entry if entry # update it as recently used
25+
entry
26+
end
27+
28+
def get_by_sid(sid)
29+
sid = Rex::Proto::MsDtyp::MsDtypSid.new(sid)
30+
31+
entry = @data.values.reverse_each.find { _1[:objectSid]&.first == sid.to_binary_s }
32+
@data[entry.dn] = entry if entry # update it as recently used
33+
entry
34+
end
35+
end
36+
37+
def ldap_entry_cache
38+
@ldap_entry_cache ||= LDAPEntryCache.new(1000)
39+
end
40+
end
41+
end
42+
end

metasploit-framework.gemspec

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,9 @@ Gem::Specification.new do |spec|
258258
# Needed to parse sections of ELF files in order to retrieve symbols
259259
spec.add_runtime_dependency 'elftools'
260260

261+
# Needed for generic in-memory cachine
262+
spec.add_runtime_dependency 'lru_redux'
263+
261264
# Standard libraries: https://www.ruby-lang.org/en/news/2023/12/25/ruby-3-3-0-released/
262265
%w[
263266
abbrev

0 commit comments

Comments
 (0)