@@ -9,6 +9,59 @@ module ActiveDirectory
9
9
include Msf ::Exploit ::Remote ::LDAP
10
10
11
11
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
12
65
13
66
# Query the remote server via the provided LDAP connection to determine if it's an Active Directory LDAP server.
14
67
# More specifically, this ensures that it reports active directory capabilities and the whoami extension.
@@ -30,6 +83,25 @@ def is_active_directory?(ldap)
30
83
true
31
84
end
32
85
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
+
33
105
def adds_query_group_members ( ldap , group_dn , base_dn : nil , inherited : true , object_class : nil )
34
106
return enum_for ( :adds_query_group_members , ldap , group_dn , base_dn : base_dn , inherited : inherited , object_class : object_class ) unless block_given?
35
107
results = 0
@@ -50,6 +122,7 @@ def adds_query_group_members(ldap, group_dn, base_dn: nil, inherited: true, obje
50
122
51
123
ldap . search (
52
124
base : base_dn || ldap . base_dn ,
125
+ controls : [ adds_build_ldap_sd_control ] ,
53
126
filter : "(&#{ filters . map { "(#{ _1 } )" } . join } )" ,
54
127
return_result : false # make sure we're streaming because this could be a lot of data
55
128
) do |ldap_entry |
@@ -85,6 +158,7 @@ def adds_query_member_groups(ldap, member_dn, base_dn: nil, inherited: true)
85
158
86
159
ldap . search (
87
160
base : base_dn || ldap . base_dn ,
161
+ controls : [ adds_build_ldap_sd_control ] ,
88
162
filter : "(&#{ filters . map { "(#{ _1 } )" } . join } )" ,
89
163
return_result : false
90
164
) do |ldap_entry |
@@ -104,26 +178,100 @@ def adds_get_object_by_dn(ldap, object_dn)
104
178
object = @ldap_objects . find { |o | o [ :dN ] &.first == object_dn }
105
179
return object if object
106
180
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
108
182
validate_query_result! ( ldap . get_operation_result . table )
109
183
110
184
@ldap_objects << object if object
111
185
object
112
186
end
113
187
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
+
114
201
def adds_get_object_by_sid ( ldap , object_sid )
115
202
@ldap_objects ||= [ ]
116
203
object_sid = Rex ::Proto ::MsDtyp ::MsDtypSid . new ( object_sid )
117
204
object = @ldap_objects . find { |o | o [ :objectSid ] &.first == object_sid . to_binary_s }
118
205
return object if object
119
206
120
207
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
122
209
validate_query_result! ( ldap . get_operation_result . table , filter )
123
210
124
211
@ldap_objects << object if object
125
212
object
126
213
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
127
275
end
128
276
end
129
277
end
0 commit comments