Skip to content

Commit d22a2b0

Browse files
author
SkelSec
committed
fixes, getting NT from pkinit
1 parent 56497fc commit d22a2b0

File tree

20 files changed

+4405
-68
lines changed

20 files changed

+4405
-68
lines changed

minikerberos/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11

2-
__version__ = "0.3.0"
2+
__version__ = "0.3.1"
33
__banner__ = \
44
"""
55
# minikerberos %s

minikerberos/aioclient.py

Lines changed: 121 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
PADATA_TYPE, PA_PAC_REQUEST, PA_ENC_TS_ENC, EncryptedData, krb5_pvno, KDC_REQ_BODY, \
1919
AS_REQ, TGS_REP, KDCOptions, PrincipalName, EncASRepPart, EncTGSRepPart, PrincipalName, Realm, \
2020
Checksum, APOptions, Authenticator, Ticket, AP_REQ, TGS_REQ, CKSUMTYPE, \
21-
PA_FOR_USER_ENC, PA_PAC_OPTIONS, PA_PAC_OPTIONSTypes
21+
PA_FOR_USER_ENC, PA_PAC_OPTIONS, PA_PAC_OPTIONSTypes, EncTicketPart, AD_IF_RELEVANT
2222

2323
from minikerberos.protocol.errors import KerberosErrorCode, KerberosError
2424
from minikerberos.protocol.encryption import Key, _enctype_table, _HMACMD5, Enctype, _checksum_table
@@ -40,6 +40,7 @@ def __init__(self, ccred:KerberosCredential, target:KerberosTarget):
4040
self.target = target
4141
self.ksoc = AIOKerberosClientSocket(self.target)
4242
self.ccache = CCACHE() if self.usercreds.ccache is None else self.usercreds.ccache
43+
self.pkinit_tkey = None
4344
self.kerberos_session_key = None
4445
self.kerberos_TGT = None
4546
self.kerberos_TGT_encpart = None
@@ -465,6 +466,78 @@ async def get_TGS(self, spn_user, override_etype = None, is_linux = False):
465466
logger.debug('Got valid TGS reply')
466467
self.kerberos_TGS = tgs
467468
return tgs, encTGSRepPart, key
469+
470+
async def U2U(self, kdcopts = ['forwardable','renewable','canonicalize', 'enc-tkt-in-skey'], supp_enc_methods = [EncryptionType.DES_CBC_CRC,EncryptionType.DES_CBC_MD4,EncryptionType.DES_CBC_MD5,EncryptionType.DES3_CBC_SHA1,EncryptionType.ARCFOUR_HMAC_MD5,EncryptionType.AES256_CTS_HMAC_SHA1_96,EncryptionType.AES128_CTS_HMAC_SHA1_96]):
471+
if not self.kerberos_TGT:
472+
logger.debug('[U2U] TGT is not available! Fetching TGT...')
473+
await self.get_TGT()
474+
475+
supp_enc = self.usercreds.get_preferred_enctype(supp_enc_methods)
476+
now = datetime.datetime.now(datetime.timezone.utc)
477+
authenticator_data = {}
478+
authenticator_data['authenticator-vno'] = krb5_pvno
479+
authenticator_data['crealm'] = Realm(self.kerberos_TGT['crealm'])
480+
authenticator_data['cname'] = self.kerberos_TGT['cname']
481+
authenticator_data['cusec'] = now.microsecond
482+
authenticator_data['ctime'] = now.replace(microsecond=0)
483+
484+
485+
authenticator_data_enc = self.kerberos_cipher.encrypt(self.kerberos_session_key, 7, Authenticator(authenticator_data).dump(), None)
486+
487+
ap_req = {}
488+
ap_req['pvno'] = krb5_pvno
489+
ap_req['msg-type'] = MESSAGE_TYPE.KRB_AP_REQ.value
490+
ap_req['ap-options'] = APOptions(set())
491+
ap_req['ticket'] = Ticket(self.kerberos_TGT['ticket'])
492+
ap_req['authenticator'] = EncryptedData({'etype': self.kerberos_cipher_type, 'cipher': authenticator_data_enc})
493+
494+
pa_data_auth = {}
495+
pa_data_auth['padata-type'] = PaDataType.TGS_REQ.value
496+
pa_data_auth['padata-value'] = AP_REQ(ap_req).dump()
497+
498+
499+
krb_tgs_body = {}
500+
krb_tgs_body['kdc-options'] = KDCOptions(set(kdcopts))
501+
krb_tgs_body['sname'] = PrincipalName({'name-type': NAME_TYPE.PRINCIPAL.value, 'name-string': [self.usercreds.username]})
502+
krb_tgs_body['realm'] = self.usercreds.domain.upper()
503+
krb_tgs_body['till'] = (now + datetime.timedelta(days=1)).replace(microsecond=0)
504+
krb_tgs_body['nonce'] = secrets.randbits(31)
505+
krb_tgs_body['etype'] = [23] # dunno why it must be 23?
506+
krb_tgs_body['additional-tickets'] = [Ticket(self.kerberos_TGT['ticket'])]
507+
508+
509+
krb_tgs_req = {}
510+
krb_tgs_req['pvno'] = krb5_pvno
511+
krb_tgs_req['msg-type'] = MESSAGE_TYPE.KRB_TGS_REQ.value
512+
krb_tgs_req['padata'] = [pa_data_auth] #pa_for_user
513+
krb_tgs_req['req-body'] = KDC_REQ_BODY(krb_tgs_body)
514+
515+
516+
517+
req = TGS_REQ(krb_tgs_req)
518+
logger.debug('[U2U] Sending request to server')
519+
520+
reply = await self.ksoc.sendrecv(req.dump())
521+
if reply.name == 'KRB_ERROR':
522+
emsg = '[U2U] failed!'
523+
if reply.native['error-code'] == 16:
524+
emsg = '[U2U] Failed to get U2U! Error code (16) indicates that delegation is not enabled for this account!'
525+
raise KerberosError(reply, emsg)
526+
527+
logger.debug('[U2U] Got reply, decrypting...')
528+
tgs = reply.native
529+
530+
cipher = _enctype_table[int(tgs['ticket']['enc-part']['etype'])]
531+
encticket = tgs['ticket']['enc-part']['cipher']
532+
decdata = cipher.decrypt(self.kerberos_session_key, 2, encticket)
533+
decticket = EncTicketPart.load(decdata).native
534+
535+
encTGSRepPart = EncTGSRepPart.load(self.kerberos_cipher.decrypt(self.kerberos_session_key, 8, tgs['enc-part']['cipher'])).native
536+
key = Key(encTGSRepPart['key']['keytype'], encTGSRepPart['key']['keyvalue'])
537+
self.ccache.add_tgs(tgs, encTGSRepPart)
538+
logger.debug('[U2U] Got valid TGS reply')
539+
540+
return tgs, encTGSRepPart, key, decticket
468541

469542
#https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-sfu/6a8dfc0c-2d32-478a-929f-5f9b1b18a169
470543
async def S4U2self(self, user_to_impersonate, spn_user = None, kdcopts = ['forwardable','renewable','canonicalize'], supp_enc_methods = [EncryptionType.DES_CBC_CRC,EncryptionType.DES_CBC_MD4,EncryptionType.DES_CBC_MD5,EncryptionType.DES3_CBC_SHA1,EncryptionType.ARCFOUR_HMAC_MD5,EncryptionType.AES256_CTS_HMAC_SHA1_96,EncryptionType.AES128_CTS_HMAC_SHA1_96]):
@@ -474,7 +547,7 @@ async def S4U2self(self, user_to_impersonate, spn_user = None, kdcopts = ['forwa
474547

475548
if not self.kerberos_TGT:
476549
logger.debug('[S4U2self] TGT is not available! Fetching TGT...')
477-
self.get_TGT()
550+
await self.get_TGT()
478551

479552
supp_enc = self.usercreds.get_preferred_enctype(supp_enc_methods)
480553
auth_package_name = 'Kerberos'
@@ -498,6 +571,7 @@ async def S4U2self(self, user_to_impersonate, spn_user = None, kdcopts = ['forwa
498571
ap_req['ticket'] = Ticket(self.kerberos_TGT['ticket'])
499572
ap_req['authenticator'] = EncryptedData({'etype': self.kerberos_cipher_type, 'cipher': authenticator_data_enc})
500573

574+
501575
pa_data_auth = {}
502576
pa_data_auth['padata-type'] = PaDataType.TGS_REQ.value
503577
pa_data_auth['padata-value'] = AP_REQ(ap_req).dump()
@@ -770,18 +844,58 @@ def truncate_key(value, keysize):
770844
etype = as_rep['enc-part']['etype']
771845
cipher = _enctype_table[etype]
772846
if etype == Enctype.AES256:
773-
t_key = truncate_key(fullKey, 32)
847+
self.pkinit_tkey = truncate_key(fullKey, 32)
774848
elif etype == Enctype.AES128:
775-
t_key = truncate_key(fullKey, 16)
849+
self.pkinit_tkey = truncate_key(fullKey, 16)
776850
elif etype == Enctype.RC4:
777851
raise NotImplementedError('RC4 key truncation documentation missing. it is different from AES')
778-
#t_key = truncate_key(fullKey, 16)
852+
#self.pkinit_tkey = truncate_key(fullKey, 16)
779853

780854

781-
key = Key(cipher.enctype, t_key)
855+
key = Key(cipher.enctype, self.pkinit_tkey)
782856
enc_data = as_rep['enc-part']['cipher']
783857
dec_data = cipher.decrypt(key, 3, enc_data)
784858
encasrep = EncASRepPart.load(dec_data).native
785859
cipher = _enctype_table[ int(encasrep['key']['keytype'])]
786860
session_key = Key(cipher.enctype, encasrep['key']['keyvalue'])
787-
return encasrep, session_key, cipher
861+
return encasrep, session_key, cipher
862+
863+
864+
def get_NT_from_PAC(self, decticket:EncTicketPart, truncated_keydata=None):
865+
from minikerberos.protocol.external.rpcrt import TypeSerialization1
866+
from minikerberos.protocol.external.pac import PACTYPE, PAC_INFO_BUFFER, \
867+
PAC_CREDENTIAL_INFO, PAC_CREDENTIAL_DATA, NTLM_SUPPLEMENTAL_CREDENTIAL
868+
869+
870+
adIfRelevant = AD_IF_RELEVANT.load(decticket['authorization-data'][0]['ad-data'])
871+
if truncated_keydata is None:
872+
truncated_keydata = self.pkinit_tkey
873+
if truncated_keydata is None:
874+
raise Exception("Missing tkey! Is this a PKINIT session?")
875+
key = Key(18, truncated_keydata)
876+
pacType = PACTYPE(adIfRelevant.native[0]['ad-data'])
877+
buff = pacType['Buffers']
878+
creds = []
879+
for bufferN in range(pacType['cBuffers']):
880+
infoBuffer = PAC_INFO_BUFFER(buff)
881+
data = pacType['Buffers'][infoBuffer['Offset']-8:][:infoBuffer['cbBufferSize']]
882+
logger.debug("TYPE 0x%x" % infoBuffer['ulType'])
883+
if infoBuffer['ulType'] == 2:
884+
credinfo = PAC_CREDENTIAL_INFO(data)
885+
newCipher = _enctype_table[credinfo['EncryptionType']]
886+
887+
out = newCipher.decrypt(key, 16, credinfo['SerializedData'])
888+
type1 = TypeSerialization1(out)
889+
# I'm skipping here 4 bytes with its the ReferentID for the pointer
890+
newdata = out[len(type1)+4:]
891+
pcc = PAC_CREDENTIAL_DATA(newdata)
892+
for cred in pcc['Credentials']:
893+
credstruct = NTLM_SUPPLEMENTAL_CREDENTIAL(b''.join(cred['Credentials']))
894+
if credstruct['NtPassword'] != b'\x00'*16:
895+
creds.append(('NT', credstruct['NtPassword'].hex()))
896+
if credstruct['LmPassword'] != b'\x00'*16:
897+
creds.append(('LM', credstruct['LmPassword'].hex()))
898+
899+
buff = buff[len(infoBuffer):]
900+
901+
return creds

minikerberos/client.py

Lines changed: 117 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
PADATA_TYPE, PA_PAC_REQUEST, PA_ENC_TS_ENC, EncryptedData, krb5_pvno, KDC_REQ_BODY, \
1818
AS_REQ, TGS_REP, KDCOptions, PrincipalName, EncASRepPart, EncTGSRepPart, PrincipalName, Realm, \
1919
Checksum, APOptions, Authenticator, Ticket, AP_REQ, TGS_REQ, CKSUMTYPE, \
20-
PA_FOR_USER_ENC, PA_PAC_OPTIONS, PA_PAC_OPTIONSTypes
20+
PA_FOR_USER_ENC, PA_PAC_OPTIONS, PA_PAC_OPTIONSTypes, EncTicketPart, AD_IF_RELEVANT
2121

2222
from minikerberos.protocol.errors import KerberosErrorCode, KerberosError
2323
from minikerberos.protocol.encryption import Key, _enctype_table, _HMACMD5, Enctype
@@ -42,6 +42,7 @@ def __init__(self, ccred, target, ccache = None):
4242
self.kerberos_cipher_type = None
4343
self.kerberos_key = None
4444
self.server_salt = None
45+
self.pkinit_tkey = None
4546

4647
@staticmethod
4748
def from_tgt(target, tgt, key):
@@ -699,18 +700,128 @@ def truncate_key(value, keysize):
699700
etype = as_rep['enc-part']['etype']
700701
cipher = _enctype_table[etype]
701702
if etype == Enctype.AES256:
702-
t_key = truncate_key(fullKey, 32)
703+
self.pkinit_tkey = truncate_key(fullKey, 32)
703704
elif etype == Enctype.AES128:
704-
t_key = truncate_key(fullKey, 16)
705+
self.pkinit_tkey = truncate_key(fullKey, 16)
705706
elif etype == Enctype.RC4:
706707
raise NotImplementedError('RC4 key truncation documentation missing. it is different from AES')
707-
#t_key = truncate_key(fullKey, 16)
708+
#self.pkinit_tkey = truncate_key(fullKey, 16)
708709

709710

710-
key = Key(cipher.enctype, t_key)
711+
key = Key(cipher.enctype, self.pkinit_tkey)
711712
enc_data = as_rep['enc-part']['cipher']
712713
dec_data = cipher.decrypt(key, 3, enc_data)
713714
encasrep = EncASRepPart.load(dec_data).native
714715
cipher = _enctype_table[ int(encasrep['key']['keytype'])]
715716
session_key = Key(cipher.enctype, encasrep['key']['keyvalue'])
716-
return encasrep, session_key, cipher
717+
return encasrep, session_key, cipher
718+
719+
def U2U(self, kdcopts = ['forwardable','renewable','canonicalize', 'enc-tkt-in-skey']):
720+
if not self.kerberos_TGT:
721+
logger.debug('[U2U] TGT is not available! Fetching TGT...')
722+
self.get_TGT()
723+
724+
now = datetime.datetime.now(datetime.timezone.utc)
725+
authenticator_data = {}
726+
authenticator_data['authenticator-vno'] = krb5_pvno
727+
authenticator_data['crealm'] = Realm(self.kerberos_TGT['crealm'])
728+
authenticator_data['cname'] = self.kerberos_TGT['cname']
729+
authenticator_data['cusec'] = now.microsecond
730+
authenticator_data['ctime'] = now.replace(microsecond=0)
731+
732+
733+
authenticator_data_enc = self.kerberos_cipher.encrypt(self.kerberos_session_key, 7, Authenticator(authenticator_data).dump(), None)
734+
735+
ap_req = {}
736+
ap_req['pvno'] = krb5_pvno
737+
ap_req['msg-type'] = MESSAGE_TYPE.KRB_AP_REQ.value
738+
ap_req['ap-options'] = APOptions(set())
739+
ap_req['ticket'] = Ticket(self.kerberos_TGT['ticket'])
740+
ap_req['authenticator'] = EncryptedData({'etype': self.kerberos_cipher_type, 'cipher': authenticator_data_enc})
741+
742+
pa_data_auth = {}
743+
pa_data_auth['padata-type'] = PaDataType.TGS_REQ.value
744+
pa_data_auth['padata-value'] = AP_REQ(ap_req).dump()
745+
746+
747+
krb_tgs_body = {}
748+
krb_tgs_body['kdc-options'] = KDCOptions(set(kdcopts))
749+
krb_tgs_body['sname'] = PrincipalName({'name-type': NAME_TYPE.PRINCIPAL.value, 'name-string': [self.usercreds.username]})
750+
krb_tgs_body['realm'] = self.usercreds.domain.upper()
751+
krb_tgs_body['till'] = (now + datetime.timedelta(days=1)).replace(microsecond=0)
752+
krb_tgs_body['nonce'] = secrets.randbits(31)
753+
krb_tgs_body['etype'] = [23] # dunno why it must be 23?
754+
krb_tgs_body['additional-tickets'] = [Ticket(self.kerberos_TGT['ticket'])]
755+
756+
757+
krb_tgs_req = {}
758+
krb_tgs_req['pvno'] = krb5_pvno
759+
krb_tgs_req['msg-type'] = MESSAGE_TYPE.KRB_TGS_REQ.value
760+
krb_tgs_req['padata'] = [pa_data_auth] #pa_for_user
761+
krb_tgs_req['req-body'] = KDC_REQ_BODY(krb_tgs_body)
762+
763+
764+
765+
req = TGS_REQ(krb_tgs_req)
766+
logger.debug('[U2U] Sending request to server')
767+
768+
reply = self.ksoc.sendrecv(req.dump())
769+
if reply.name == 'KRB_ERROR':
770+
emsg = '[U2U] failed!'
771+
if reply.native['error-code'] == 16:
772+
emsg = '[U2U] Failed to get U2U! Error code (16) indicates that delegation is not enabled for this account!'
773+
raise KerberosError(reply, emsg)
774+
775+
logger.debug('[U2U] Got reply, decrypting...')
776+
tgs = reply.native
777+
778+
cipher = _enctype_table[int(tgs['ticket']['enc-part']['etype'])]
779+
encticket = tgs['ticket']['enc-part']['cipher']
780+
decdata = cipher.decrypt(self.kerberos_session_key, 2, encticket)
781+
decticket = EncTicketPart.load(decdata).native
782+
783+
encTGSRepPart = EncTGSRepPart.load(self.kerberos_cipher.decrypt(self.kerberos_session_key, 8, tgs['enc-part']['cipher'])).native
784+
key = Key(encTGSRepPart['key']['keytype'], encTGSRepPart['key']['keyvalue'])
785+
self.ccache.add_tgs(tgs, encTGSRepPart)
786+
logger.debug('[U2U] Got valid TGS reply')
787+
788+
return tgs, encTGSRepPart, key, decticket
789+
790+
def get_NT_from_PAC(self, decticket:EncTicketPart, truncated_keydata=None):
791+
from minikerberos.protocol.external.rpcrt import TypeSerialization1
792+
from minikerberos.protocol.external.pac import PACTYPE, PAC_INFO_BUFFER, \
793+
PAC_CREDENTIAL_INFO, PAC_CREDENTIAL_DATA, NTLM_SUPPLEMENTAL_CREDENTIAL
794+
795+
796+
adIfRelevant = AD_IF_RELEVANT.load(decticket['authorization-data'][0]['ad-data'])
797+
if truncated_keydata is None:
798+
truncated_keydata = self.pkinit_tkey
799+
if truncated_keydata is None:
800+
raise Exception("Missing tkey! Is this a PKINIT session?")
801+
key = Key(18, truncated_keydata)
802+
pacType = PACTYPE(adIfRelevant.native[0]['ad-data'])
803+
buff = pacType['Buffers']
804+
creds = []
805+
for bufferN in range(pacType['cBuffers']):
806+
infoBuffer = PAC_INFO_BUFFER(buff)
807+
data = pacType['Buffers'][infoBuffer['Offset']-8:][:infoBuffer['cbBufferSize']]
808+
logger.debug("TYPE 0x%x" % infoBuffer['ulType'])
809+
if infoBuffer['ulType'] == 2:
810+
credinfo = PAC_CREDENTIAL_INFO(data)
811+
newCipher = _enctype_table[credinfo['EncryptionType']]
812+
813+
out = newCipher.decrypt(key, 16, credinfo['SerializedData'])
814+
type1 = TypeSerialization1(out)
815+
# I'm skipping here 4 bytes with its the ReferentID for the pointer
816+
newdata = out[len(type1)+4:]
817+
pcc = PAC_CREDENTIAL_DATA(newdata)
818+
for cred in pcc['Credentials']:
819+
credstruct = NTLM_SUPPLEMENTAL_CREDENTIAL(b''.join(cred['Credentials']))
820+
if credstruct['NtPassword'] != b'\x00'*16:
821+
creds.append(('NT', credstruct['NtPassword'].hex()))
822+
if credstruct['LmPassword'] != b'\x00'*16:
823+
creds.append(('LM', credstruct['LmPassword'].hex()))
824+
825+
buff = buff[len(infoBuffer):]
826+
827+
return creds

minikerberos/common/constants.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
import enum
22

3-
class KerberosSocketType(enum.Enum):
4-
UDP = enum.auto()
5-
TCP = enum.auto()
6-
73
class KerberosSecretType(enum.Enum):
84
PASSWORD = 'PASSWORD'
95
PW = 'PW'
@@ -20,4 +16,5 @@ class KerberosSecretType(enum.Enum):
2016
KEYTAB = 'KEYTAB'
2117
KIRBI = 'KIRBI'
2218
PFX = 'PFX'
23-
PEM = 'PEM'
19+
PEM = 'PEM'
20+
PFXSTR = 'PFXSTR'

0 commit comments

Comments
 (0)