@@ -9,6 +9,11 @@ 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
12
17
13
18
# Query the remote server via the provided LDAP connection to determine if it's an Active Directory LDAP server.
14
19
# More specifically, this ensures that it reports active directory capabilities and the whoami extension.
@@ -30,6 +35,25 @@ def is_active_directory?(ldap)
30
35
true
31
36
end
32
37
38
+ def adds_build_ldap_sd_control
39
+ # Set the value of LDAP_SERVER_SD_FLAGS_OID flag so everything but
40
+ # the SACL flag is set, as we need administrative privileges to retrieve
41
+ # the SACL from the ntSecurityDescriptor attribute on Windows AD LDAP servers.
42
+ #
43
+ # Note that without specifying the LDAP_SERVER_SD_FLAGS_OID control in this manner,
44
+ # the LDAP searchRequest will default to trying to grab all possible attributes of
45
+ # the ntSecurityDescriptor attribute, hence resulting in an attempt to retrieve the
46
+ # SACL even if the user is not an administrative user.
47
+ #
48
+ # Now one may think that we would just get the rest of the data without the SACL field,
49
+ # however in reality LDAP will cause that attribute to just be blanked out if a part of it
50
+ # cannot be retrieved, so we just will get nothing for the ntSecurityDescriptor attribute
51
+ # in these cases if the user doesn't have permissions to read the SACL.
52
+ all_but_sacl_flag = OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION
53
+ control_values = [ all_but_sacl_flag ] . map ( &:to_ber ) . to_ber_sequence . to_s . to_ber
54
+ [ LDAP_SERVER_SD_FLAGS_OID . to_ber , true . to_ber , control_values ] . to_ber_sequence
55
+ end
56
+
33
57
def adds_query_group_members ( ldap , group_dn , base_dn : nil , inherited : true , object_class : nil )
34
58
return enum_for ( :adds_query_group_members , ldap , group_dn , base_dn : base_dn , inherited : inherited , object_class : object_class ) unless block_given?
35
59
results = 0
@@ -50,6 +74,7 @@ def adds_query_group_members(ldap, group_dn, base_dn: nil, inherited: true, obje
50
74
51
75
ldap . search (
52
76
base : base_dn || ldap . base_dn ,
77
+ controls : [ adds_build_ldap_sd_control ] ,
53
78
filter : "(&#{ filters . map { "(#{ _1 } )" } . join } )" ,
54
79
return_result : false # make sure we're streaming because this could be a lot of data
55
80
) do |ldap_entry |
@@ -85,6 +110,7 @@ def adds_query_member_groups(ldap, member_dn, base_dn: nil, inherited: true)
85
110
86
111
ldap . search (
87
112
base : base_dn || ldap . base_dn ,
113
+ controls : [ adds_build_ldap_sd_control ] ,
88
114
filter : "(&#{ filters . map { "(#{ _1 } )" } . join } )" ,
89
115
return_result : false
90
116
) do |ldap_entry |
@@ -104,26 +130,86 @@ def adds_get_object_by_dn(ldap, object_dn)
104
130
object = @ldap_objects . find { |o | o [ :dN ] &.first == object_dn }
105
131
return object if object
106
132
107
- object = ldap . search ( base : object_dn , scope : Net ::LDAP ::SearchScope_BaseObject ) &.first
133
+ object = ldap . search ( base : object_dn , controls : [ adds_build_ldap_sd_control ] , scope : Net ::LDAP ::SearchScope_BaseObject ) &.first
108
134
validate_query_result! ( ldap . get_operation_result . table )
109
135
110
136
@ldap_objects << object if object
111
137
object
112
138
end
113
139
140
+ def adds_get_object_by_samaccountname ( ldap , object_samaccountname )
141
+ @ldap_objects ||= [ ]
142
+ object = @ldap_objects . find { |o | o [ :sAMAccountName ] &.first == object_samaccountname }
143
+ return object if object
144
+
145
+ filter = "(sAMAccountName=#{ ldap_escape_filter ( object_samaccountname ) } )"
146
+ object = ldap . search ( base : ldap . base_dn , controls : [ adds_build_ldap_sd_control ] , filter : filter ) &.first
147
+ validate_query_result! ( ldap . get_operation_result . table , filter )
148
+
149
+ @ldap_objects << object if object
150
+ object
151
+ end
152
+
114
153
def adds_get_object_by_sid ( ldap , object_sid )
115
154
@ldap_objects ||= [ ]
116
155
object_sid = Rex ::Proto ::MsDtyp ::MsDtypSid . new ( object_sid )
117
156
object = @ldap_objects . find { |o | o [ :objectSid ] &.first == object_sid . to_binary_s }
118
157
return object if object
119
158
120
159
filter = "(objectSID=#{ ldap_escape_filter ( object_sid . to_s ) } )"
121
- object = ldap . search ( base : ldap . base_dn , filter : filter ) &.first
160
+ object = ldap . search ( base : ldap . base_dn , controls : [ adds_build_ldap_sd_control ] , filter : filter ) &.first
122
161
validate_query_result! ( ldap . get_operation_result . table , filter )
123
162
124
163
@ldap_objects << object if object
125
164
object
126
165
end
166
+
167
+ def adds_get_current_user ( ldap )
168
+ our_domain , _ , our_username = ldap . ldapwhoami . to_s . delete_prefix ( 'u:' ) . partition ( '\\' )
169
+ # todo: this is probably going to have issues if our user is from a domain that the target server is not the
170
+ # authority of
171
+ adds_get_object_by_samaccountname ( ldap , our_username )
172
+ end
173
+
174
+ def adds_sd_grants_permissions? ( ldap , security_descriptor , matcher , test_sid : nil )
175
+ unless test_sid
176
+ current_user = adds_get_current_user ( ldap )
177
+ test_sid = Rex ::Proto ::MsDtyp ::MsDtypSid . read ( current_user [ :objectSid ] . first )
178
+ end
179
+
180
+ # todo: process special SIDs, e.g. SELF, Everyone
181
+
182
+ test_member_sids = nil
183
+ security_descriptor . dacl . aces . each do |ace |
184
+ # only processing simple allow and deny types right now
185
+ if ace . header . ace_type == Rex ::Proto ::MsDtyp ::MsDtypAceType ::ACCESS_ALLOWED_ACE_TYPE
186
+ eval_result = true
187
+ elsif ace . header . ace_type == Rex ::Proto ::MsDtyp ::MsDtypAceType ::ACCESS_DENIED_ACE_TYPE
188
+ eval_result = false
189
+ else
190
+ next # skip ACEs that are neither allow or deny
191
+ end
192
+
193
+ next unless matcher . call ( ace )
194
+
195
+ return eval_result if ace . body . sid == test_sid
196
+
197
+ ldap_object = adds_get_object_by_sid ( ldap , ace . body . sid )
198
+ next unless ldap_object && ldap_object [ :objectClass ] . include? ( 'group' )
199
+
200
+ member_sids = adds_query_group_members ( ldap , ldap_object [ :dN ] . first , inherited : false ) . map { |member | Rex ::Proto ::MsDtyp ::MsDtypSid . read ( member [ :objectSid ] . first ) }
201
+ return eval_result if member_sids . include? ( test_sid )
202
+
203
+ if test_member_sids . nil?
204
+ test_obj = adds_get_object_by_sid ( ldap , test_sid )
205
+ test_member_sids = adds_query_member_groups ( ldap , test_obj [ :dN ] . first , inherited : true ) . map { |member | Rex ::Proto ::MsDtyp ::MsDtypSid . read ( member [ :objectSid ] . first ) }
206
+ end
207
+
208
+ return eval_result if member_sids . any? { |member_sid | test_member_sids . include? ( member_sid ) }
209
+ end
210
+
211
+ false
212
+ end
127
213
end
128
214
end
129
215
end
0 commit comments