# SECUREAUTH LABS. Copyright 2019 SecureAuth Corporation. All rights reserved.
#
# This software is provided under under a slightly modified version
# of the Apache Software License. See the accompanying LICENSE file
# for more information.
#
# Author: Alberto Solino (@agsolino)
#
# Description:
# Helper functions for kerberos
# Just starting, TONS of things to do
# In fact, make it easier
#
import datetime
import random
import socket
import struct
import os
from pyasn1.codec.der import decoder, encoder
from pyasn1.error import PyAsn1Error
from pyasn1.type.univ import noValue
from six import b
from binascii import unhexlify, hexlify
from impacket.krb5.asn1 import AS_REQ, AP_REQ, TGS_REQ, KERB_PA_PAC_REQUEST, KRB_ERROR, PA_ENC_TS_ENC, AS_REP, TGS_REP, \
EncryptedData, Authenticator, EncASRepPart, EncTGSRepPart, seq_set, seq_set_iter, KERB_ERROR_DATA, METHOD_DATA, \
ETYPE_INFO2, ETYPE_INFO, AP_REP, EncAPRepPart
from impacket.krb5.types import KerberosTime, Principal, Ticket
from impacket.krb5.gssapi import CheckSumField, GSS_C_DCE_STYLE, GSS_C_MUTUAL_FLAG, GSS_C_REPLAY_FLAG, \
GSS_C_SEQUENCE_FLAG, GSS_C_CONF_FLAG, GSS_C_INTEG_FLAG
from impacket.krb5 import constants
from impacket.krb5.crypto import Key, _enctype_table, InvalidChecksum
from impacket.smbconnection import SessionError
from impacket.spnego import SPNEGO_NegTokenInit, TypesMech, SPNEGO_NegTokenResp, ASN1_OID, asn1encode, ASN1_AID
from impacket.krb5.gssapi import KRB5_AP_REQ
from impacket import nt_errors, LOG
from impacket.krb5.ccache import CCache
# Our random number generator
try:
rand = random.SystemRandom()
except NotImplementedError:
rand = random
pass
def sendReceive(data, host, kdcHost):
if kdcHost is None:
targetHost = host
else:
targetHost = kdcHost
messageLen = struct.pack('!i', len(data))
LOG.debug('Trying to connect to KDC at %s' % targetHost)
try:
af, socktype, proto, canonname, sa = socket.getaddrinfo(targetHost, 88, 0, socket.SOCK_STREAM)[0]
s = socket.socket(af, socktype, proto)
s.connect(sa)
except socket.error as e:
raise socket.error("Connection error (%s:%s)" % (targetHost, 88), e)
s.sendall(messageLen + data)
recvDataLen = struct.unpack('!i', s.recv(4))[0]
r = s.recv(recvDataLen)
69 ↛ 70line 69 didn't jump to line 70, because the condition on line 69 was never true while len(r) < recvDataLen:
r += s.recv(recvDataLen-len(r))
try:
krbError = KerberosError(packet = decoder.decode(r, asn1Spec = KRB_ERROR())[0])
except:
return r
77 ↛ 78line 77 didn't jump to line 78, because the condition on line 77 was never true if krbError.getErrorCode() != constants.ErrorCodes.KDC_ERR_PREAUTH_REQUIRED.value:
raise krbError
return r
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)
129 ↛ 130line 129 didn't jump to line 130, because the condition on line 129 was never true 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
140 ↛ 141line 140 didn't jump to line 141, because the condition on line 140 was never true 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 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 = ''
192 ↛ 194line 192 didn't jump to line 194, because the condition on line 192 was never true 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:
217 ↛ 218line 217 didn't jump to line 218, because the condition on line 217 was never true 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)
238 ↛ 313line 238 didn't jump to line 313, because the condition on line 238 was never false if preAuth is True:
239 ↛ 240line 239 didn't jump to line 240, because the condition on line 239 was never 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 b'' and nthash is b'' and (aesKey is 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']
315 ↛ 317line 315 didn't jump to line 317, because the condition on line 315 was never true 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 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'] = 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'] = 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()
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'] = rand.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)
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, cipherText)
encTGSRepPart = decoder.decode(plainText, asn1Spec = EncTGSRepPart())[0]
newSessionKey = Key(encTGSRepPart['key']['keytype'], encTGSRepPart['key']['keyvalue'].asOctets())
# 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')
451 ↛ 456line 451 didn't jump to line 456, because the condition on line 451 was never false 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)
################################################################################
# DCE RPC Helpers
################################################################################
def getKerberosType3(cipher, sessionKey, auth_data):
negTokenResp = SPNEGO_NegTokenResp(auth_data)
# If DCE_STYLE = FALSE
#ap_rep = decoder.decode(negTokenResp['ResponseToken'][16:], asn1Spec=AP_REP())[0]
try:
krbError = KerberosError(packet = decoder.decode(negTokenResp['ResponseToken'][15:], asn1Spec = KRB_ERROR())[0])
except Exception:
pass
else:
raise krbError
ap_rep = decoder.decode(negTokenResp['ResponseToken'], asn1Spec=AP_REP())[0]
cipherText = ap_rep['enc-part']['cipher']
# Key Usage 12
# AP-REP encrypted part (includes application session
# subkey), encrypted with the application session key
# (Section 5.5.2)
plainText = cipher.decrypt(sessionKey, 12, cipherText)
encAPRepPart = decoder.decode(plainText, asn1Spec = EncAPRepPart())[0]
cipher = _enctype_table[int(encAPRepPart['subkey']['keytype'])]()
sessionKey2 = Key(cipher.enctype, encAPRepPart['subkey']['keyvalue'].asOctets())
sequenceNumber = int(encAPRepPart['seq-number'])
encAPRepPart['subkey'].clear()
encAPRepPart = encAPRepPart.clone()
now = datetime.datetime.utcnow()
encAPRepPart['cusec'] = now.microsecond
encAPRepPart['ctime'] = KerberosTime.to_asn1(now)
encAPRepPart['seq-number'] = sequenceNumber
encodedAuthenticator = encoder.encode(encAPRepPart)
encryptedEncodedAuthenticator = cipher.encrypt(sessionKey, 12, encodedAuthenticator, None)
ap_rep['enc-part'].clear()
ap_rep['enc-part']['etype'] = cipher.enctype
ap_rep['enc-part']['cipher'] = encryptedEncodedAuthenticator
resp = SPNEGO_NegTokenResp()
resp['ResponseToken'] = encoder.encode(ap_rep)
return cipher, sessionKey2, resp.getData()
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
524 ↛ 530line 524 didn't jump to line 530, because the condition on line 524 was never false if isinstance(aesKey, str):
try:
aesKey = unhexlify(aesKey)
except TypeError:
pass
530 ↛ 567line 530 didn't jump to line 567, because the condition on line 530 was never false if TGT is None and TGS is None:
531 ↛ 567line 531 didn't jump to line 567, because the condition on line 531 was never false 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:
569 ↛ 591line 569 didn't jump to line 591, because the condition on line 569 was never false if TGT is None:
570 ↛ 597line 570 didn't jump to line 597, because the condition on line 570 was never false 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 b'' and nthash is b'' and (aesKey is 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
597 ↛ 619line 597 didn't jump to line 619, because the condition on line 597 was never false 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 b'' and nthash is b'' and (aesKey is 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()
class SessionKeyDecryptionError(Exception):
"""
Exception risen when we fail to decrypt a session key within an AS-REP
message.
It provides context information such as full AS-REP message but also the
cipher, key and cipherText used when the error occurred.
"""
def __init__( self, message, asRep, cipher, key, cipherText):
self.message = message
self.asRep = asRep
self.cipher = cipher
self.key = key
self.cipherText = cipherText
def __str__ ( self):
return "SessionKeyDecryptionError: %s" % self.message
class KerberosError(SessionError):
"""
This is the exception every client should catch regardless of the underlying
SMB version used. We'll take care of that. NETBIOS exceptions are NOT included,
since all SMB versions share the same NETBIOS instances.
"""
def __init__( self, error = 0, packet=0):
SessionError.__init__(self)
self.error = error
self.packet = packet
712 ↛ exitline 712 didn't return from function '__init__', because the condition on line 712 was never false if packet != 0:
self.error = self.packet['error-code']
def getErrorCode( self ):
return self.error
def getErrorPacket( self ):
return self.packet
def getErrorString( self ):
return constants.ERROR_MESSAGES[self.error]
def __str__( self ):
retString = 'Kerberos SessionError: %s(%s)' % (constants.ERROR_MESSAGES[self.error])
try:
# Let's try to get the NT ERROR, if not, we quit and give the general one
if self.error == constants.ErrorCodes.KRB_ERR_GENERIC.value:
eData = decoder.decode(self.packet['e-data'], asn1Spec = KERB_ERROR_DATA())[0]
nt_error = struct.unpack('<L', eData['data-value'].asOctets()[:4])[0]
retString += '\nNT ERROR: %s(%s)' % (nt_errors.ERROR_MESSAGES[nt_error])
except:
pass
return retString
|