Skip to content

Commit e9564e4

Browse files
committed
Initial implementation of #adds_sd_grants_permissions?
1 parent 08323d9 commit e9564e4

File tree

2 files changed

+152
-2
lines changed

2 files changed

+152
-2
lines changed

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

Lines changed: 150 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,59 @@ module ActiveDirectory
99
include Msf::Exploit::Remote::LDAP
1010

1111
LDAP_CAP_ACTIVE_DIRECTORY_OID = '1.2.840.113556.1.4.800'.freeze
12+
LDAP_SERVER_SD_FLAGS_OID = '1.2.840.113556.1.4.801'.freeze
13+
OWNER_SECURITY_INFORMATION = 0x1
14+
GROUP_SECURITY_INFORMATION = 0x2
15+
DACL_SECURITY_INFORMATION = 0x4
16+
SACL_SECURITY_INFORMATION = 0x8
17+
18+
class ACEMatcherAllowAll
19+
attr_reader :permissions
20+
21+
def initialize(permissions)
22+
@permissions = Array.wrap(permissions)
23+
end
24+
25+
def call(ace)
26+
access_mask = ace.body.access_mask.snapshot
27+
target_permissions = access_mask.select { |flag, value| @permissions.include?(flag) }
28+
29+
if target_permissions.empty?
30+
return false
31+
elsif Rex::Proto::MsDtyp::MsDtypAceType.allowed?(ace.header.ace_type)
32+
return target_permissions.all? { |flag, value| value == 1 }
33+
elsif Rex::Proto::MsDtyp::MsDtypAceType.denied?(ace.header.ace_type)
34+
# if any target permission is denied, then all of them can't be allowed
35+
return target_permissions.any? { |flag, value| value == 0 }
36+
end
37+
38+
false
39+
end
40+
end
41+
42+
class ACEMatcherAllowAny
43+
attr_reader :permissions
44+
45+
def initialize(permissions)
46+
@permissions = Array.wrap(permissions)
47+
end
48+
49+
def call(ace)
50+
access_mask = ace.body.access_mask.snapshot
51+
target_permissions = access_mask.select { |flag, value| @permissions.include?(flag) }
52+
53+
if target_permissions.empty?
54+
return false
55+
elsif Rex::Proto::MsDtyp::MsDtypAceType.allowed?(ace.header.ace_type)
56+
return target_permissions.any? { |flag, value| value == 1 }
57+
elsif Rex::Proto::MsDtyp::MsDtypAceType.denied?(ace.header.ace_type)
58+
# if all target permissions are denied, then none of them can be allowed
59+
return target_permissions.all? { |flag, value| value == 0 }
60+
end
61+
62+
false
63+
end
64+
end
1265

1366
# Query the remote server via the provided LDAP connection to determine if it's an Active Directory LDAP server.
1467
# More specifically, this ensures that it reports active directory capabilities and the whoami extension.
@@ -30,6 +83,25 @@ def is_active_directory?(ldap)
3083
true
3184
end
3285

86+
def adds_build_ldap_sd_control
87+
# Set the value of LDAP_SERVER_SD_FLAGS_OID flag so everything but
88+
# the SACL flag is set, as we need administrative privileges to retrieve
89+
# the SACL from the ntSecurityDescriptor attribute on Windows AD LDAP servers.
90+
#
91+
# Note that without specifying the LDAP_SERVER_SD_FLAGS_OID control in this manner,
92+
# the LDAP searchRequest will default to trying to grab all possible attributes of
93+
# the ntSecurityDescriptor attribute, hence resulting in an attempt to retrieve the
94+
# SACL even if the user is not an administrative user.
95+
#
96+
# Now one may think that we would just get the rest of the data without the SACL field,
97+
# however in reality LDAP will cause that attribute to just be blanked out if a part of it
98+
# cannot be retrieved, so we just will get nothing for the ntSecurityDescriptor attribute
99+
# in these cases if the user doesn't have permissions to read the SACL.
100+
all_but_sacl_flag = OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION
101+
control_values = [all_but_sacl_flag].map(&:to_ber).to_ber_sequence.to_s.to_ber
102+
[LDAP_SERVER_SD_FLAGS_OID.to_ber, true.to_ber, control_values].to_ber_sequence
103+
end
104+
33105
def adds_query_group_members(ldap, group_dn, base_dn: nil, inherited: true, object_class: nil)
34106
return enum_for(:adds_query_group_members, ldap, group_dn, base_dn: base_dn, inherited: inherited, object_class: object_class) unless block_given?
35107
results = 0
@@ -50,6 +122,7 @@ def adds_query_group_members(ldap, group_dn, base_dn: nil, inherited: true, obje
50122

51123
ldap.search(
52124
base: base_dn || ldap.base_dn,
125+
controls: [adds_build_ldap_sd_control],
53126
filter: "(&#{filters.map { "(#{_1})" }.join})",
54127
return_result: false # make sure we're streaming because this could be a lot of data
55128
) do |ldap_entry|
@@ -85,6 +158,7 @@ def adds_query_member_groups(ldap, member_dn, base_dn: nil, inherited: true)
85158

86159
ldap.search(
87160
base: base_dn || ldap.base_dn,
161+
controls: [adds_build_ldap_sd_control],
88162
filter: "(&#{filters.map { "(#{_1})" }.join})",
89163
return_result: false
90164
) do |ldap_entry|
@@ -104,26 +178,100 @@ def adds_get_object_by_dn(ldap, object_dn)
104178
object = @ldap_objects.find { |o| o[:dN]&.first == object_dn }
105179
return object if object
106180

107-
object = ldap.search(base: object_dn, scope: Net::LDAP::SearchScope_BaseObject)&.first
181+
object = ldap.search(base: object_dn, controls: [adds_build_ldap_sd_control], scope: Net::LDAP::SearchScope_BaseObject)&.first
108182
validate_query_result!(ldap.get_operation_result.table)
109183

110184
@ldap_objects << object if object
111185
object
112186
end
113187

188+
def adds_get_object_by_samaccountname(ldap, object_samaccountname)
189+
@ldap_objects ||= []
190+
object = @ldap_objects.find { |o| o[:sAMAccountName]&.first == object_samaccountname }
191+
return object if object
192+
193+
filter = "(sAMAccountName=#{ldap_escape_filter(object_samaccountname)})"
194+
object = ldap.search(base: ldap.base_dn, controls: [adds_build_ldap_sd_control], filter: filter)&.first
195+
validate_query_result!(ldap.get_operation_result.table, filter)
196+
197+
@ldap_objects << object if object
198+
object
199+
end
200+
114201
def adds_get_object_by_sid(ldap, object_sid)
115202
@ldap_objects ||= []
116203
object_sid = Rex::Proto::MsDtyp::MsDtypSid.new(object_sid)
117204
object = @ldap_objects.find { |o| o[:objectSid]&.first == object_sid.to_binary_s }
118205
return object if object
119206

120207
filter = "(objectSID=#{ldap_escape_filter(object_sid.to_s)})"
121-
object = ldap.search(base: ldap.base_dn, filter: filter)&.first
208+
object = ldap.search(base: ldap.base_dn, controls: [adds_build_ldap_sd_control], filter: filter)&.first
122209
validate_query_result!(ldap.get_operation_result.table, filter)
123210

124211
@ldap_objects << object if object
125212
object
126213
end
214+
215+
def adds_get_current_user(ldap)
216+
our_domain, _, our_username = ldap.ldapwhoami.to_s.delete_prefix('u:').partition('\\')
217+
# todo: this is probably going to have issues if our user is from a domain that the target server is not the
218+
# authority of
219+
adds_get_object_by_samaccountname(ldap, our_username)
220+
end
221+
222+
def adds_sd_grants_permissions?(ldap, security_descriptor, matcher, test_sid: nil, self_sid: nil)
223+
unless test_sid
224+
current_user = adds_get_current_user(ldap)
225+
test_sid = Rex::Proto::MsDtyp::MsDtypSid.read(current_user[:objectSid].first)
226+
end
227+
228+
test_member_sids = nil
229+
security_descriptor.dacl.aces.each do |ace|
230+
# only processing simple allow and deny types right now
231+
case ace.header.ace_type
232+
when Rex::Proto::MsDtyp::MsDtypAceType::ACCESS_ALLOWED_ACE_TYPE
233+
eval_result = true
234+
when Rex::Proto::MsDtyp::MsDtypAceType::ACCESS_ALLOWED_OBJECT_ACE_TYPE
235+
eval_result = true
236+
when Rex::Proto::MsDtyp::MsDtypAceType::ACCESS_DENIED_ACE_TYPE
237+
eval_result = false
238+
when Rex::Proto::MsDtyp::MsDtypAceType::ACCESS_DENIED_OBJECT_ACE_TYPE
239+
eval_result = false
240+
else
241+
next # skip ACEs that are neither allow or deny
242+
end
243+
244+
next unless matcher.call(ace)
245+
246+
case ace.body.sid
247+
when Rex::Proto::Secauthz::WellKnownSids::SECURITY_WORLD_SID
248+
return eval_result
249+
when Rex::Proto::Secauthz::WellKnownSids::SECURITY_PRINCIPAL_SELF_SID
250+
return eval_result if self_sid == test_sid
251+
when Rex::Proto::Secauthz::WellKnownSids::SECURITY_CREATOR_OWNER_SID
252+
return eval_result if security_descriptor.owner_sid == test_sid
253+
when Rex::Proto::Secauthz::WellKnownSids::SECURITY_CREATOR_GROUP_SID
254+
return eval_result if security_descriptor.group_sid == test_sid
255+
when test_sid
256+
return eval_result
257+
else
258+
ldap_object = adds_get_object_by_sid(ldap, ace.body.sid)
259+
next unless ldap_object && ldap_object[:objectClass].include?('group')
260+
261+
member_sids = adds_query_group_members(ldap, ldap_object[:dN].first, inherited: false).map { |member| Rex::Proto::MsDtyp::MsDtypSid.read(member[:objectSid].first) }
262+
return eval_result if member_sids.include?(test_sid)
263+
264+
if test_member_sids.nil?
265+
test_obj = adds_get_object_by_sid(ldap, test_sid)
266+
test_member_sids = adds_query_member_groups(ldap, test_obj[:dN].first, inherited: true).map { |member| Rex::Proto::MsDtyp::MsDtypSid.read(member[:objectSid].first) }
267+
end
268+
269+
return eval_result if member_sids.any? { |member_sid| test_member_sids.include?(member_sid) }
270+
end
271+
end
272+
273+
false
274+
end
127275
end
128276
end
129277
end

lib/rex/proto/secauthz/well_known_sids.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ module WellKnownSids
1919
SECURITY_CREATOR_OWNER_RID = 0
2020
SECURITY_CREATOR_GROUP_RID = 1
2121

22+
SECURITY_WORLD_SID = "#{SECURITY_WORLD_SID_AUTHORITY}-#{SECURITY_WORLD_RID}"
23+
2224
SECURITY_CREATOR_OWNER_SID = "#{SECURITY_CREATOR_SID_AUTHORITY}-#{SECURITY_CREATOR_OWNER_RID}"
2325
SECURITY_CREATOR_GROUP_SID = "#{SECURITY_CREATOR_SID_AUTHORITY}-#{SECURITY_CREATOR_GROUP_RID}"
2426

0 commit comments

Comments
 (0)