Skip to content

Commit 6b10517

Browse files
committed
Expand on the matcher logic
This is necessary to account for effects from multiple ACEs.
1 parent 85324af commit 6b10517

File tree

5 files changed

+214
-118
lines changed

5 files changed

+214
-118
lines changed

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

Lines changed: 20 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ module Exploit::Remote::LDAP
88
module ActiveDirectory
99
include Msf::Exploit::Remote::LDAP
1010
include Msf::Exploit::Remote::LDAP::EntryCache
11+
include Msf::Exploit::Remote::LDAP::ActiveDirectory::SecurityDescriptorMatcher
1112

1213
LDAP_CAP_ACTIVE_DIRECTORY_OID = '1.2.840.113556.1.4.800'.freeze
1314
LDAP_SERVER_SD_FLAGS_OID = '1.2.840.113556.1.4.801'.freeze
@@ -16,56 +17,6 @@ module ActiveDirectory
1617
DACL_SECURITY_INFORMATION = 0x4
1718
SACL_SECURITY_INFORMATION = 0x8
1819

19-
# Match an ACE when *any* of the specified permissions are allowed or *all* of the specified permissions are
20-
# denied.
21-
class ACEMatcherAllowAll
22-
attr_reader :permissions
23-
24-
def initialize(permissions)
25-
@permissions = Array.wrap(permissions)
26-
end
27-
28-
def call(ace)
29-
target_permissions = ace.body.access_mask.permissions
30-
31-
if target_permissions.empty?
32-
return false
33-
elsif Rex::Proto::MsDtyp::MsDtypAceType.allowed?(ace.header.ace_type)
34-
return @permissions.all? { |perm| target_permissions.include?(perm) }
35-
elsif Rex::Proto::MsDtyp::MsDtypAceType.denied?(ace.header.ace_type)
36-
# if any target permission is denied, then all of them can't be allowed
37-
return @permissions.any? { |perm| target_permissions.include?(perm) }
38-
end
39-
40-
false
41-
end
42-
end
43-
44-
# Match an ACE when *all* of the specified permissions are allowed or *any* of the specified permissions are
45-
# denied.
46-
class ACEMatcherAllowAny
47-
attr_reader :permissions
48-
49-
def initialize(permissions)
50-
@permissions = Array.wrap(permissions)
51-
end
52-
53-
def call(ace)
54-
target_permissions = ace.body.access_mask.permissions
55-
56-
if target_permissions.empty?
57-
return false
58-
elsif Rex::Proto::MsDtyp::MsDtypAceType.allowed?(ace.header.ace_type)
59-
return @permissions.any? { |perm| target_permissions.include?(perm) }
60-
elsif Rex::Proto::MsDtyp::MsDtypAceType.denied?(ace.header.ace_type)
61-
# if all target permissions are denied, then none of them can be allowed
62-
return @permissions.all? { |perm| target_permissions.include?(perm) }
63-
end
64-
65-
false
66-
end
67-
end
68-
6920
# Query the remote server via the provided LDAP connection to determine if it's an Active Directory LDAP server.
7021
# More specifically, this ensures that it reports active directory capabilities and the whoami extension.
7122
#
@@ -310,51 +261,45 @@ def adds_sd_grants_permissions?(ldap, security_descriptor, matcher, test_sid: ni
310261
end
311262

312263
test_member_sids = nil
313-
security_descriptor.dacl.aces.each do |ace|
314-
# only processing simple allow and deny types right now
315-
case ace.header.ace_type
316-
when Rex::Proto::MsDtyp::MsDtypAceType::ACCESS_ALLOWED_ACE_TYPE
317-
eval_result = true
318-
when Rex::Proto::MsDtyp::MsDtypAceType::ACCESS_ALLOWED_OBJECT_ACE_TYPE
319-
eval_result = true
320-
when Rex::Proto::MsDtyp::MsDtypAceType::ACCESS_DENIED_ACE_TYPE
321-
eval_result = false
322-
when Rex::Proto::MsDtyp::MsDtypAceType::ACCESS_DENIED_OBJECT_ACE_TYPE
323-
eval_result = false
324-
else
325-
next # skip ACEs that are neither allow or deny
326-
end
327264

328-
next unless matcher.call(ace)
265+
dacl_aces = []
266+
# because deny entries take precedence, process them first
267+
dacl_aces += security_descriptor.dacl.aces.select { |ace| Rex::Proto::MsDtyp::MsDtypAceType.deny?(ace.header.ace_type) }
268+
dacl_aces += security_descriptor.dacl.aces.select { |ace| Rex::Proto::MsDtyp::MsDtypAceType.allow?(ace.header.ace_type) }
269+
270+
dacl_aces.each do |ace|
271+
next if matcher.ignore_ace?(ace)
329272

330273
case ace.body.sid
331274
when Rex::Proto::Secauthz::WellKnownSids::SECURITY_WORLD_SID
332-
return eval_result
275+
matcher.apply_ace!(ace)
333276
when Rex::Proto::Secauthz::WellKnownSids::SECURITY_PRINCIPAL_SELF_SID
334-
return eval_result if self_sid == test_sid
277+
matcher.apply_ace!(ace) if self_sid == test_sid
335278
when Rex::Proto::Secauthz::WellKnownSids::SECURITY_CREATOR_OWNER_SID
336-
return eval_result if security_descriptor.owner_sid == test_sid
279+
matcher.apply_ace!(ace) if security_descriptor.owner_sid == test_sid
337280
when Rex::Proto::Secauthz::WellKnownSids::SECURITY_CREATOR_GROUP_SID
338-
return eval_result if security_descriptor.group_sid == test_sid
281+
matcher.apply_ace!(ace) if security_descriptor.group_sid == test_sid
339282
when test_sid
340-
return eval_result
283+
matcher.apply_ace!(ace)
341284
else
342285
ldap_object = adds_get_object_by_sid(ldap, ace.body.sid)
343286
next unless ldap_object && ldap_object[:objectClass].include?('group')
344287

345-
member_sids = adds_query_group_members(ldap, ldap_object[:dN].first, inherited: false).map { |member| Rex::Proto::MsDtyp::MsDtypSid.read(member[:objectSid].first) }
346-
return eval_result if member_sids.include?(test_sid)
288+
member_sids = adds_query_group_members(ldap, ldap_object.dn, inherited: false).map { |member| Rex::Proto::MsDtyp::MsDtypSid.read(member[:objectSid].first) }
289+
matcher.apply_ace!(ace) if member_sids.include?(test_sid)
347290

348291
if test_member_sids.nil?
349292
test_obj = adds_get_object_by_sid(ldap, test_sid)
350-
test_member_sids = adds_query_member_groups(ldap, test_obj[:dN].first, inherited: true).map { |member| Rex::Proto::MsDtyp::MsDtypSid.read(member[:objectSid].first) }
293+
test_member_sids = adds_query_member_groups(ldap, test_obj.dn, inherited: true).map { |member| Rex::Proto::MsDtyp::MsDtypSid.read(member[:objectSid].first) }
351294
end
352295

353-
return eval_result if member_sids.any? { |member_sid| test_member_sids.include?(member_sid) }
296+
matcher.apply_ace!(ace) if member_sids.any? { |member_sid| test_member_sids.include?(member_sid) }
354297
end
298+
299+
break if matcher.satisfied?
355300
end
356301

357-
false
302+
matcher.affirmative?
358303
end
359304

360305
# Determine if a security descriptor will grant the permissions identified by *matcher* to the
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
module Msf
2+
###
3+
#
4+
# This module exposes methods for querying a remote LDAP service
5+
#
6+
###
7+
module Exploit::Remote::LDAP::ActiveDirectory
8+
9+
module SecurityDescriptorMatcher
10+
# Match an ACE when *any* of the specified permissions are allowed or *all* of the specified permissions are
11+
# denied.
12+
class Allow
13+
attr_reader :permissions
14+
15+
def initialize(permission, object_id: nil)
16+
@permission = permission
17+
@object_id = object_id
18+
@result = nil
19+
end
20+
21+
def ignore_ace?(ace)
22+
# ignore anything that's not an allow or deny ACE because those are the only two that will alter the outcome
23+
return true unless Rex::Proto::MsDtyp::MsDtypAceType.allow?(ace.header.ace_type) || Rex::Proto::MsDtyp::MsDtypAceType.deny?(ace.header.ace_type)
24+
25+
if Rex::Proto::MsDtyp::MsDtypAceType.has_object?(ace.header.ace_type)
26+
return true if ace.body.object_type != @object_id
27+
else
28+
return true if @object_id
29+
end
30+
31+
!ace.body.access_mask.permissions.include?(@permission)
32+
end
33+
34+
def apply_ace!(ace)
35+
return if ignore_ace?(ace)
36+
37+
@result = ace.header.ace_type
38+
end
39+
40+
def satisfied?
41+
# A matcher is satisfied when there's nothing left for it to check.
42+
!@result.nil?
43+
end
44+
45+
def affirmative?
46+
# This is named affirmative? instead of allow? so that other matchers can be made in the future
47+
# to match on the desired outcome including audit and alarm events. We are affirming that the
48+
# security descriptor will apply the desired affect which in this case is to allow access.
49+
satisfied? && Rex::Proto::MsDtyp::MsDtypAceType.allow?(@result)
50+
end
51+
52+
def self.any(permissions, object_id: nil)
53+
permissions = Array.wrap(permissions)
54+
MultipleAll.new(permissions.map { |permission| new(permission, object_id: object_id) })
55+
end
56+
57+
def self.all(permissions, object_id: nil)
58+
permissions = Array.wrap(permissions)
59+
MultipleAll.new(permissions.map { |permission| new(permission, object_id: object_id) })
60+
end
61+
end
62+
63+
class MultipleAny
64+
attr_reader :matchers
65+
66+
def initialize(matchers)
67+
@matchers = Array.wrap(matchers)
68+
end
69+
70+
def ignore_ace?(ace)
71+
@matchers.all? { |matcher| matcher.ignore_ace?(ace) }
72+
end
73+
74+
def apply_ace!(ace)
75+
@matchers.each do |matcher|
76+
next if matcher.ignore_ace?(ace)
77+
78+
matcher.apply_ace!(ace)
79+
end
80+
end
81+
82+
def satisfied?
83+
@matchers.any? { |matcher| matcher.satisfied? }
84+
end
85+
86+
def affirmative?
87+
@matchers.any? { |matcher| matcher.affirmative? }
88+
end
89+
end
90+
91+
class MultipleAll < MultipleAny
92+
def satisfied?
93+
@matchers.all? { |matcher| matcher.satisfied? }
94+
end
95+
96+
def affirmative?
97+
@matchers.all? { |matcher| matcher.affirmative? }
98+
end
99+
end
100+
end
101+
end
102+
end

lib/rex/proto/ms_dtyp.rb

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ def self.alarm?(type)
340340
].include? type
341341
end
342342

343-
def self.allowed?(type)
343+
def self.allow?(type)
344344
[
345345
ACCESS_ALLOWED_ACE_TYPE,
346346
ACCESS_ALLOWED_COMPOUND_ACE_TYPE,
@@ -359,14 +359,27 @@ def self.audit?(type)
359359
].include? type
360360
end
361361

362-
def self.denied?(type)
362+
def self.deny?(type)
363363
[
364364
ACCESS_DENIED_ACE_TYPE,
365365
ACCESS_DENIED_OBJECT_ACE_TYPE,
366366
ACCESS_DENIED_CALLBACK_ACE_TYPE,
367367
ACCESS_DENIED_CALLBACK_OBJECT_ACE_TYPE,
368368
].include? type
369369
end
370+
371+
def self.has_object?(type)
372+
[
373+
ACCESS_ALLOWED_OBJECT_ACE_TYPE,
374+
ACCESS_DENIED_OBJECT_ACE_TYPE,
375+
SYSTEM_AUDIT_OBJECT_ACE_TYPE,
376+
SYSTEM_ALARM_OBJECT_ACE_TYPE,
377+
ACCESS_ALLOWED_CALLBACK_OBJECT_ACE_TYPE,
378+
ACCESS_DENIED_CALLBACK_OBJECT_ACE_TYPE,
379+
SYSTEM_AUDIT_CALLBACK_OBJECT_ACE_TYPE,
380+
SYSTEM_ALARM_CALLBACK_OBJECT_ACE_TYPE
381+
].include? type
382+
end
370383
end
371384

372385
# [2.4.4.1 ACE_HEADER](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/628ebb1d-c509-4ea0-a10f-77ef97ca4586)

modules/auxiliary/admin/ldap/rbcd.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ def check
115115
return Exploit::CheckCode::Unknown('Failed to find the specified object.')
116116
end
117117

118-
unless adds_obj_grants_permissions?(@ldap, obj, ACEMatcherAllowAll.new(%i[RP WP]))
118+
unless adds_obj_grants_permissions?(@ldap, obj, SecurityDescriptorMatcher::Allow.all(%i[RP WP]))
119119
return Exploit::CheckCode::Safe('The object can not be written to.')
120120
end
121121

0 commit comments

Comments
 (0)