class Ticket(object):
    def __init__(self):
        # This is the kerberos version, not the service principal key
        # version number.
        self.tkt_vno = None
        self.service_principal = None
        self.encrypted_part = None

    def from_asn1(self, data):
        data = _asn1_decode(data, asn1.Ticket())
        self.tkt_vno = int(data.getComponentByName('tkt-vno'))
        self.service_principal = PrincipalModified()
        self.service_principal.from_asn1(data, 'realm', 'sname')
        self.encrypted_part = EncryptedData()
        self.encrypted_part.from_asn1(data.getComponentByName('enc-part'))
        return self

    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 __str__(self):
        return "<Ticket for %s vno %s>" % (str(
            self.service_principal), str(self.encrypted_part.kvno))
 def from_asn1(self, data):
     data = _asn1_decode(data, asn1.Ticket())
     self.tkt_vno = int(data.getComponentByName('tkt-vno'))
     self.service_principal = PrincipalModified()
     self.service_principal.from_asn1(data, 'realm', 'sname')
     self.encrypted_part = EncryptedData()
     self.encrypted_part.from_asn1(data.getComponentByName('enc-part'))
     return self
Example #3
0
    # Pass the hash/aes key :P
    if nthash != '':
        key = Key(cipher.enctype, nthash)
    elif aesKey != '':
        key = Key(cipher.enctype, aesKey.decode('hex'))
    else:
        key = cipher.string_to_key(password, encryptionTypesData[enctype],
                                   None)
    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'] = None
    asReq['padata'][0] = None
    asReq['padata'][0]['padata-type'] = int(
        constants.PreAuthenticationDataTypes.PA_ENC_TIMESTAMP.value)
    asReq['padata'][0]['padata-value'] = encodedEncryptedData
Example #4
0
    def _parseForREQ(self, timestamp, asn_data, ip_packet):
        # decode data just to parse to see if it's a KDC_REQ packet
        try:
            asn = decoder.decode(asn_data)[0]
            if asn[0] != 5:
                return None
        except:
            return None

        # check to see if it's an AS_REQ or TGS_REQ
        if asn[1] != constants.ApplicationTagNumbers.AS_REQ.value and asn[
                1] != constants.ApplicationTagNumbers.TGS_REQ.value:
            return None

        # try decoding (both AS_REQ and TGS_REQ are KDC_REQ packets)
        try:
            req = decoder.decode(asn_data, asn1Spec=AS_REQ())[0]
        except:
            req = decoder.decode(asn_data, asn1Spec=TGS_REQ())[0]

        crealm = req['req-body']['realm']
        cname = self._getPrinc(req['req-body']['cname'])
        trealm = req['req-body']['realm']
        tname = self._getPrinc(req['req-body']['sname'])

        for padata in req['padata']:
            # extract encrypted authenticators from AS_REQ packets
            if padata[
                    'padata-type'] == constants.PreAuthenticationDataTypes.PA_ENC_TIMESTAMP.value:
                auth = decoder.decode(padata['padata-value'],
                                      asn1Spec=EncryptedData())[0]
                if auth['etype'] == Enctype.DES_CRC:
                    cenc = str(auth['cipher'])

            # extract encrypted ticket data from TGS_REQ packets
            if padata[
                    'padata-type'] == constants.PreAuthenticationDataTypes.PA_TGS_REQ.value:
                asn1 = decoder.decode(padata['padata-value'])[0]

                if asn1[0] != 5 or asn1[1] != 14:
                    return None

                ap = decoder.decode(padata['padata-value'],
                                    asn1Spec=AP_REQ())[0]
                trealm = ap['ticket']['realm']
                tname = self._getPrinc(ap['ticket']['sname'])

                if ap['authenticator']['etype'] == Enctype.DES_CRC:
                    cenc = str(ap['authenticator']['cipher'])

                if ap['ticket']['enc-part']['etype'] == Enctype.DES_CRC:
                    tenc = str(ap['ticket']['enc-part']['cipher'])

        try:
            tenc_packet = KerbPacket(asn, socket.inet_ntoa(ip_packet.src),
                                     socket.inet_ntoa(ip_packet.dst), asn[1],
                                     cname, crealm, tname, trealm, tenc, 1,
                                     timestamp)
        except:
            tenc_packet = None

        try:
            cenc_packet = KerbPacket(asn, socket.inet_ntoa(ip_packet.src),
                                     socket.inet_ntoa(ip_packet.dst), asn[1],
                                     cname, crealm, tname, trealm, cenc, 0,
                                     timestamp)
        except:
            cenc_packet = None

        return tenc_packet, cenc_packet
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
Example #6
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