1414from OpenSSL .SSL import SysCallError
1515from bloodhound .ad .authentication import ADAuthentication
1616from bloodhound .ad .domain import AD
17- from impacket .dcerpc .v5 .epm import MSRPC_UUID_PORTMAP
18- from impacket .dcerpc .v5 .rpcrt import DCERPCException , RPC_C_AUTHN_GSS_NEGOTIATE
1917from impacket .dcerpc .v5 .samr import (
2018 UF_ACCOUNTDISABLE ,
2119 UF_DONT_REQUIRE_PREAUTH ,
2220 UF_TRUSTED_FOR_DELEGATION ,
2321 UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION ,
2422 UF_SERVER_TRUST_ACCOUNT ,
2523)
26- from impacket .dcerpc .v5 .transport import DCERPCTransportFactory
2724from impacket .krb5 import constants
2825from impacket .krb5 .kerberosv5 import getKerberosTGS , SessionKeyDecryptionError
2926from impacket .krb5 .types import Principal , KerberosException
3027from impacket .ldap import ldap as ldap_impacket
3128from impacket .ldap import ldaptypes
3229from impacket .ldap import ldapasn1 as ldapasn1_impacket
3330from impacket .ldap .ldap import LDAPFilterSyntaxError
34- from impacket .smb import SMB_DIALECT
35- from impacket .smbconnection import SMBConnection , SessionError
31+ from impacket .smbconnection import SessionError
32+ from impacket .ntlm import getNTLMSSPType1
3633
3734from nxc .config import process_secret , host_info_colors
3835from nxc .connection import connection
4239from nxc .protocols .ldap .gmsa import MSDS_MANAGEDPASSWORD_BLOB
4340from nxc .protocols .ldap .kerberos import KerberosAttacks
4441from nxc .parsers .ldap_results import parse_result_attributes
42+ from nxc .helpers .ntlm_parser import parse_challenge
4543
4644ldap_error_status = {
4745 "1" : "STATUS_NOT_SUPPORTED" ,
@@ -136,7 +134,7 @@ def __init__(self, args, db, host):
136134 self .server_os = None
137135 self .os_arch = 0
138136 self .hash = None
139- self .ldapConnection = None
137+ self .ldap_connection = None
140138 self .lmhash = ""
141139 self .nthash = ""
142140 self .baseDN = ""
@@ -163,25 +161,25 @@ def proto_logger(self):
163161 }
164162 )
165163
166- def get_ldap_info (self , host ):
164+ def create_conn_obj (self ):
167165 try :
168166 proto = "ldaps" if (self .args .gmsa or self .port == 636 ) else "ldap"
169- ldap_url = f"{ proto } ://{ host } "
167+ ldap_url = f"{ proto } ://{ self . host } "
170168 self .logger .info (f"Connecting to { ldap_url } with no baseDN" )
171169 try :
172- ldap_connection = ldap_impacket .LDAPConnection (ldap_url , dstIp = self .host )
173- if ldap_connection :
174- self .logger .debug (f"ldap_connection: { ldap_connection } " )
170+ self . ldap_connection = ldap_impacket .LDAPConnection (ldap_url , dstIp = self .host )
171+ if self . ldap_connection :
172+ self .logger .debug (f"ldap_connection: { self . ldap_connection } " )
175173 except SysCallError as e :
176174 if proto == "ldaps" :
177175 self .logger .fail (f"LDAPs connection to { ldap_url } failed - { e } " )
178176 # https://learn.microsoft.com/en-us/troubleshoot/windows-server/identity/enable-ldap-over-ssl-3rd-certification-authority
179177 self .logger .fail ("Even if the port is open, LDAPS may not be configured" )
180178 else :
181179 self .logger .fail (f"LDAP connection to { ldap_url } failed: { e } " )
182- exit ( 1 )
180+ return False
183181
184- resp = ldap_connection .search (
182+ resp = self . ldap_connection .search (
185183 scope = ldapasn1_impacket .Scope ("baseObject" ),
186184 attributes = ["defaultNamingContext" , "dnsHostName" ],
187185 sizeLimit = 0 ,
@@ -208,42 +206,18 @@ def get_ldap_info(self, host):
208206 self .logger .debug ("Exception:" , exc_info = True )
209207 self .logger .info (f"Skipping item, cannot process due to error { e } " )
210208 except OSError :
211- return [ None , None , None ]
209+ return False
212210 self .logger .debug (f"Target: { target } ; target_domain: { target_domain } ; base_dn: { base_dn } " )
213- return [target , target_domain , base_dn ]
214-
215- def get_os_arch (self ):
216- try :
217- string_binding = rf"ncacn_ip_tcp:{ self .host } [135]"
218- transport = DCERPCTransportFactory (string_binding )
219- transport .setRemoteHost (self .host )
220- transport .set_connect_timeout (5 )
221- dce = transport .get_dce_rpc ()
222- if self .args .kerberos :
223- dce .set_auth_type (RPC_C_AUTHN_GSS_NEGOTIATE )
224- dce .connect ()
225- try :
226- dce .bind (
227- MSRPC_UUID_PORTMAP ,
228- transfer_syntax = ("71710533-BEBA-4937-8319-B5DBEF9CCC36" , "1.0" ),
229- )
230- except DCERPCException as e :
231- if str (e ).find ("syntaxes_not_supported" ) >= 0 :
232- dce .disconnect ()
233- return 32
234- else :
235- dce .disconnect ()
236- return 64
237- except Exception as e :
238- self .logger .fail (f"Error retrieving os arch of { self .host } : { e !s} " )
239-
240- return 0
211+ self .target = target
212+ self .targetDomain = target_domain
213+ self .baseDN = base_dn
214+ return True
241215
242216 def get_ldap_username (self ):
243217 extended_request = ldapasn1_impacket .ExtendedRequest ()
244218 extended_request ["requestName" ] = "1.3.6.1.4.1.4203.1.11.3" # whoami
245219
246- response = self .ldapConnection .sendReceive (extended_request )
220+ response = self .ldap_connection .sendReceive (extended_request )
247221 for message in response :
248222 search_result = message ["protocolOp" ].getComponent ()
249223 if search_result ["resultCode" ] == ldapasn1_impacket .ResultCode ("success" ):
@@ -254,46 +228,26 @@ def get_ldap_username(self):
254228 return ""
255229
256230 def enum_host_info (self ):
257- self .target , self .targetDomain , self .baseDN = self .get_ldap_info (self .host )
258231 self .baseDN = self .args .base_dn if self .args .base_dn else self .baseDN # Allow overwriting baseDN from args
259- self .hostname = self .target
232+ self .hostname = self .target . split ( "." )[ 0 ]. upper ()
260233 self .remoteName = self .target
261234 self .domain = self .targetDomain
262- # smb no open, specify the domain
263- if not self .args .no_smb :
264- self .local_ip = self .conn .getSMBServer ().get_socket ().getsockname ()[0 ]
265235
266- try :
267- self .conn .login ("" , "" )
268- except BrokenPipeError as e :
269- self .logger .fail (f"Broken Pipe Error while attempting to login: { e } " )
270- except Exception as e :
271- if "STATUS_NOT_SUPPORTED" in str (e ):
272- self .no_ntlm = True
273- if not self .no_ntlm :
274- self .hostname = self .conn .getServerName ()
275- self .targetDomain = self .domain = self .conn .getServerDNSDomainName ()
276- self .server_os = self .conn .getServerOS ()
277- self .signing = self .conn .isSigningRequired () if self .smbv1 else self .conn ._SMBConnection ._Connection ["RequireSigning" ]
278- self .os_arch = self .get_os_arch ()
279- self .logger .extra ["hostname" ] = self .hostname
280-
281- if not self .domain :
282- self .domain = self .hostname
283- if self .args .domain :
284- self .domain = self .args .domain
285- if self .args .local_auth :
286- self .domain = self .hostname
287- self .remoteName = self .host if not self .kerberos else f"{ self .hostname } .{ self .domain } "
288-
289- try : # noqa: SIM105
290- # DC's seem to want us to logoff first, windows workstations sometimes reset the connection
291- self .conn .logoff ()
292- except Exception :
293- pass
294-
295- # Re-connect since we logged off
296- self .create_conn_obj ()
236+ ntlm_challenge = None
237+ bindRequest = ldapasn1_impacket .BindRequest ()
238+ bindRequest ["version" ] = 3
239+ bindRequest ["name" ] = ""
240+ negotiate = getNTLMSSPType1 ()
241+ bindRequest ["authentication" ]["sicilyNegotiate" ] = negotiate .getData ()
242+ try :
243+ response = self .ldap_connection .sendReceive (bindRequest )[0 ]["protocolOp" ]
244+ ntlm_challenge = bytes (response ["bindResponse" ]["matchedDN" ])
245+ except Exception as e :
246+ self .logger .debug (f"Failed to get target { self .host } ntlm challenge, error: { e !s} " )
247+
248+ if ntlm_challenge :
249+ ntlm_info = parse_challenge (ntlm_challenge )
250+ self .server_os = ntlm_info ["os_version" ]
297251
298252 if not self .kdcHost and self .domain :
299253 result = self .resolver (self .domain )
@@ -304,17 +258,10 @@ def enum_host_info(self):
304258
305259 def print_host_info (self ):
306260 self .logger .debug ("Printing host info for LDAP" )
307- if self .args .no_smb :
308- self .logger .extra ["protocol" ] = "LDAP" if self .port == 389 else "LDAPS"
309- self .logger .extra ["port" ] = self .port
310- self .logger .display (f'{ self .baseDN } (Hostname: { self .hostname .split ("." )[0 ]} ) (domain: { self .domain } )' )
311- else :
312- self .logger .extra ["protocol" ] = "SMB" if not self .no_ntlm else "LDAP"
313- self .logger .extra ["port" ] = "445" if not self .no_ntlm else "389"
314- signing = colored (f"signing:{ self .signing } " , host_info_colors [0 ], attrs = ["bold" ]) if self .signing else colored (f"signing:{ self .signing } " , host_info_colors [1 ], attrs = ["bold" ])
315- smbv1 = colored (f"SMBv1:{ self .smbv1 } " , host_info_colors [2 ], attrs = ["bold" ]) if self .smbv1 else colored (f"SMBv1:{ self .smbv1 } " , host_info_colors [3 ], attrs = ["bold" ])
316- self .logger .display (f"{ self .server_os } { f' x{ self .os_arch } ' if self .os_arch else '' } (name:{ self .hostname } ) (domain:{ self .targetDomain } ) ({ signing } ) ({ smbv1 } )" )
317- self .logger .extra ["protocol" ] = "LDAP"
261+ self .logger .extra ["protocol" ] = "LDAP" if str (self .port ) == "389" else "LDAPS"
262+ self .logger .extra ["port" ] = self .port
263+ self .logger .extra ["hostname" ] = self .hostname
264+ self .logger .display (f"{ self .server_os } (name:{ self .hostname } ) (domain:{ self .domain } )" )
318265
319266 def kerberos_login (self , domain , username , password = "" , ntlm_hash = "" , aesKey = "" , kdcHost = "" , useCache = False ):
320267 self .username = username
@@ -355,8 +302,8 @@ def kerberos_login(self, domain, username, password="", ntlm_hash="", aesKey="",
355302 proto = "ldaps" if (self .args .gmsa or self .port == 636 ) else "ldap"
356303 ldap_url = f"{ proto } ://{ self .target } "
357304 self .logger .info (f"Connecting to { ldap_url } - { self .baseDN } - { self .host } [1]" )
358- self .ldapConnection = ldap_impacket .LDAPConnection (url = ldap_url , baseDN = self .baseDN , dstIp = self .host )
359- self .ldapConnection .kerberosLogin (username , password , domain , self .lmhash , self .nthash , aesKey , kdcHost = kdcHost , useCache = useCache )
305+ self .ldap_connection = ldap_impacket .LDAPConnection (url = ldap_url , baseDN = self .baseDN , dstIp = self .host )
306+ self .ldap_connection .kerberosLogin (username , password , domain , self .lmhash , self .nthash , aesKey , kdcHost = kdcHost , useCache = useCache )
360307 if self .username == "" :
361308 self .username = self .get_ldap_username ()
362309
@@ -400,8 +347,8 @@ def kerberos_login(self, domain, username, password="", ntlm_hash="", aesKey="",
400347 self .logger .extra ["port" ] = "636"
401348 ldaps_url = f"ldaps://{ self .target } "
402349 self .logger .info (f"Connecting to { ldaps_url } - { self .baseDN } - { self .host } [2]" )
403- self .ldapConnection = ldap_impacket .LDAPConnection (url = ldaps_url , baseDN = self .baseDN , dstIp = self .host )
404- self .ldapConnection .kerberosLogin (username , password , domain , self .lmhash , self .nthash , aesKey , kdcHost = kdcHost , useCache = useCache )
350+ self .ldap_connection = ldap_impacket .LDAPConnection (url = ldaps_url , baseDN = self .baseDN , dstIp = self .host )
351+ self .ldap_connection .kerberosLogin (username , password , domain , self .lmhash , self .nthash , aesKey , kdcHost = kdcHost , useCache = useCache )
405352 if self .username == "" :
406353 self .username = self .get_ldap_username ()
407354
@@ -457,8 +404,8 @@ def plaintext_login(self, domain, username, password):
457404 proto = "ldaps" if (self .args .gmsa or self .port == 636 ) else "ldap"
458405 ldap_url = f"{ proto } ://{ self .target } "
459406 self .logger .info (f"Connecting to { ldap_url } - { self .baseDN } - { self .host } [3]" )
460- self .ldapConnection = ldap_impacket .LDAPConnection (url = ldap_url , baseDN = self .baseDN , dstIp = self .host )
461- self .ldapConnection .login (self .username , self .password , self .domain , self .lmhash , self .nthash )
407+ self .ldap_connection = ldap_impacket .LDAPConnection (url = ldap_url , baseDN = self .baseDN , dstIp = self .host )
408+ self .ldap_connection .login (self .username , self .password , self .domain , self .lmhash , self .nthash )
462409 self .check_if_admin ()
463410
464411 # Prepare success credential text
@@ -478,8 +425,8 @@ def plaintext_login(self, domain, username, password):
478425 self .logger .extra ["port" ] = "636"
479426 ldaps_url = f"ldaps://{ self .target } "
480427 self .logger .info (f"Connecting to { ldaps_url } - { self .baseDN } - { self .host } [4]" )
481- self .ldapConnection = ldap_impacket .LDAPConnection (url = ldaps_url , baseDN = self .baseDN , dstIp = self .host )
482- self .ldapConnection .login (self .username , self .password , self .domain , self .lmhash , self .nthash )
428+ self .ldap_connection = ldap_impacket .LDAPConnection (url = ldaps_url , baseDN = self .baseDN , dstIp = self .host )
429+ self .ldap_connection .login (self .username , self .password , self .domain , self .lmhash , self .nthash )
483430 self .check_if_admin ()
484431
485432 # Prepare success credential text
@@ -543,8 +490,8 @@ def hash_login(self, domain, username, ntlm_hash):
543490 proto = "ldaps" if (self .args .gmsa or self .port == 636 ) else "ldap"
544491 ldaps_url = f"{ proto } ://{ self .target } "
545492 self .logger .info (f"Connecting to { ldaps_url } - { self .baseDN } - { self .host } " )
546- self .ldapConnection = ldap_impacket .LDAPConnection (url = ldaps_url , baseDN = self .baseDN , dstIp = self .host )
547- self .ldapConnection .login (self .username , self .password , self .domain , self .lmhash , self .nthash )
493+ self .ldap_connection = ldap_impacket .LDAPConnection (url = ldaps_url , baseDN = self .baseDN , dstIp = self .host )
494+ self .ldap_connection .login (self .username , self .password , self .domain , self .lmhash , self .nthash )
548495 self .check_if_admin ()
549496
550497 # Prepare success credential text
@@ -564,8 +511,8 @@ def hash_login(self, domain, username, ntlm_hash):
564511 self .logger .extra ["port" ] = "636"
565512 ldaps_url = f"{ proto } ://{ self .target } "
566513 self .logger .info (f"Connecting to { ldaps_url } - { self .baseDN } - { self .host } " )
567- self .ldapConnection = ldap_impacket .LDAPConnection (url = ldaps_url , baseDN = self .baseDN , dstIp = self .host )
568- self .ldapConnection .login (self .username , self .password , self .domain , self .lmhash , self .nthash )
514+ self .ldap_connection = ldap_impacket .LDAPConnection (url = ldaps_url , baseDN = self .baseDN , dstIp = self .host )
515+ self .ldap_connection .login (self .username , self .password , self .domain , self .lmhash , self .nthash )
569516 self .check_if_admin ()
570517
571518 # Prepare success credential text
@@ -594,40 +541,6 @@ def hash_login(self, domain, username, ntlm_hash):
594541 self .logger .fail (f"{ self .domain } \\ { self .username } :{ process_secret (self .password )} { 'Error connecting to the domain, are you sure LDAP service is running on the target?' } \n Error: { e } " )
595542 return False
596543
597- def create_smbv1_conn (self ):
598- self .logger .debug ("Creating smbv1 connection object" )
599- try :
600- self .conn = SMBConnection (self .host , self .host , None , 445 , preferredDialect = SMB_DIALECT )
601- self .smbv1 = True
602- if self .conn :
603- self .logger .debug ("SMBv1 Connection successful" )
604- except OSError as e :
605- if str (e ).find ("Connection reset by peer" ) != - 1 :
606- self .logger .debug (f"SMBv1 might be disabled on { self .host } " )
607- return False
608- except Exception as e :
609- self .logger .debug (f"Error creating SMBv1 connection to { self .host } : { e } " )
610- return False
611- return True
612-
613- def create_smbv3_conn (self ):
614- self .logger .debug ("Creating smbv3 connection object" )
615- try :
616- self .conn = SMBConnection (self .host , self .host , None , 445 )
617- self .smbv1 = False
618- if self .conn :
619- self .logger .debug ("SMBv3 Connection successful" )
620- except OSError :
621- return False
622- except Exception as e :
623- self .logger .debug (f"Error creating SMBv3 connection to { self .host } : { e } " )
624- return False
625-
626- return True
627-
628- def create_conn_obj (self ):
629- return bool (self .args .no_smb or self .create_smbv1_conn () or self .create_smbv3_conn ())
630-
631544 def get_sid (self ):
632545 self .logger .highlight (f"Domain SID { self .sid_domain } " )
633546
@@ -692,12 +605,12 @@ def getUnixTime(self, t):
692605
693606 def search (self , searchFilter , attributes , sizeLimit = 0 ) -> list :
694607 try :
695- if self .ldapConnection :
608+ if self .ldap_connection :
696609 self .logger .debug (f"Search Filter={ searchFilter } " )
697610
698611 # Microsoft Active Directory set an hard limit of 1000 entries returned by any search
699612 paged_search_control = ldapasn1_impacket .SimplePagedResultsControl (criticality = True , size = 1000 )
700- return self .ldapConnection .search (
613+ return self .ldap_connection .search (
701614 searchBase = self .baseDN ,
702615 searchFilter = searchFilter ,
703616 attributes = attributes ,
@@ -1245,7 +1158,7 @@ def password_not_required(self):
12451158 searchFilter = "(userAccountControl:1.2.840.113556.1.4.803:=32)"
12461159 try :
12471160 self .logger .debug (f"Search Filter={ searchFilter } " )
1248- resp = self .ldapConnection .search (
1161+ resp = self .ldap_connection .search (
12491162 searchBase = self .baseDN ,
12501163 searchFilter = searchFilter ,
12511164 attributes = [
@@ -1373,7 +1286,7 @@ def admin_count(self):
13731286 def gmsa (self ):
13741287 self .logger .display ("Getting GMSA Passwords" )
13751288 search_filter = "(objectClass=msDS-GroupManagedServiceAccount)"
1376- gmsa_accounts = self .ldapConnection .search (
1289+ gmsa_accounts = self .ldap_connection .search (
13771290 searchBase = self .baseDN ,
13781291 searchFilter = search_filter ,
13791292 attributes = [
@@ -1426,7 +1339,7 @@ def gmsa_convert_id(self):
14261339 else :
14271340 # getting the gmsa account
14281341 search_filter = "(objectClass=msDS-GroupManagedServiceAccount)"
1429- gmsa_accounts = self .ldapConnection .search (
1342+ gmsa_accounts = self .ldap_connection .search (
14301343 searchBase = self .baseDN ,
14311344 searchFilter = search_filter ,
14321345 attributes = ["sAMAccountName" ],
@@ -1456,7 +1369,7 @@ def gmsa_decrypt_lsa(self):
14561369 gmsa_pass = gmsa [1 ]
14571370 # getting the gmsa account
14581371 search_filter = "(objectClass=msDS-GroupManagedServiceAccount)"
1459- gmsa_accounts = self .ldapConnection .search (
1372+ gmsa_accounts = self .ldap_connection .search (
14601373 searchBase = self .baseDN ,
14611374 searchFilter = search_filter ,
14621375 attributes = ["sAMAccountName" ],
0 commit comments