diff --git a/examples/ticketer.py b/examples/ticketer.py index 487bb592e..c0219a32f 100755 --- a/examples/ticketer.py +++ b/examples/ticketer.py @@ -1084,9 +1084,14 @@ def signEncryptTicket(self, kdcRep, encASorTGSRepPart, encTicketPart, pacInfos): return encoder.encode(kdcRep), cipher, sessionKey def saveTicket(self, ticket, sessionKey): - logging.info('Saving ticket in %s' % (self.__target.replace('/', '.') + '.ccache')) + logging.info('Saving/Updating ticket in %s' % (self.__target.replace('/', '.') + '.ccache')) from impacket.krb5.ccache import CCache - ccache = CCache() + from os import getenv, path + krb5 = getenv('KRB5CCNAME') + if krb5 and path.isfile(krb5): + ccache = CCache.loadFile(krb5) + else: + ccache = CCache() if self.__server == self.__domain: ccache.fromTGT(ticket, sessionKey, sessionKey) diff --git a/impacket/krb5/ccache.py b/impacket/krb5/ccache.py index aedc764cd..36af15c55 100644 --- a/impacket/krb5/ccache.py +++ b/impacket/krb5/ccache.py @@ -451,13 +451,18 @@ def toTimeStamp(self, dt, epoch=datetime(1970,1,1)): return int((td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) // 1e6) def reverseFlags(self, flags): - result = 0 + # If passed as a string representation (e.g., "'010100...'B"), clean it up if isinstance(flags, str): - flags = flags[1:-2] - for i,j in enumerate(reversed(flags)): - if j != 0: - result += j << i - return result + bit_str = flags.replace("'", "").replace("B", "") + else: + # Standard pyasn1 BitString handling: join the bits into a string + bit_str = "".join(str(bit) for bit in flags) + + # Ensure it's exactly 32 bits by padding with zeros on the right + bit_str = bit_str.ljust(32, '0')[:32] + + # Convert the base-2 binary string back to an integer + return int(bit_str, 2) def fromTGT(self, tgt, oldSessionKey, sessionKey): self.headers = [] @@ -675,100 +680,135 @@ def saveKirbiFile(self, fileName): f.close() def fromKRBCRED(self, encodedKrbCred): - krbCred = decoder.decode(encodedKrbCred, asn1Spec=KRB_CRED())[0] - encKrbCredPart = decoder.decode(krbCred['enc-part']['cipher'], asn1Spec=EncKrbCredPart())[0] - krbCredInfo = encKrbCredPart['ticket-info'][0] + encKrbCredPart = decoder.decode( + krbCred["enc-part"]["cipher"], asn1Spec=EncKrbCredPart() + )[0] self.setDefaultHeader() - tmpPrincipal = types.Principal() - tmpPrincipal.from_asn1(krbCredInfo, 'prealm', 'pname') - self.principal = Principal() - self.principal.fromPrincipal(tmpPrincipal) - - credential = Credential() - server = types.Principal() - server.from_asn1(krbCredInfo, 'srealm', 'sname') - tmpServer = Principal() - tmpServer.fromPrincipal(server) - - credential['client'] = self.principal - credential['server'] = tmpServer - credential['is_skey'] = 0 - - credential['key'] = KeyBlockV4() - credential['key']['keytype'] = int(krbCredInfo['key']['keytype']) - credential['key']['keyvalue'] = krbCredInfo['key']['keyvalue'].asOctets() - credential['key']['keylen'] = len(credential['key']['keyvalue']) - - credential['time'] = Times() - - credential['time']['authtime'] = self.toTimeStamp(types.KerberosTime.from_asn1(krbCredInfo['starttime'])) - credential['time']['starttime'] = self.toTimeStamp(types.KerberosTime.from_asn1(krbCredInfo['starttime'])) - credential['time']['endtime'] = self.toTimeStamp(types.KerberosTime.from_asn1(krbCredInfo['endtime'])) - # After KB4586793 for CVE-2020-17049 this timestamp may be omitted - if krbCredInfo['renew-till'].hasValue(): - credential['time']['renew_till'] = self.toTimeStamp(types.KerberosTime.from_asn1(krbCredInfo['renew-till'])) - - flags = self.reverseFlags(krbCredInfo['flags']) - credential['tktflags'] = flags - - credential['num_address'] = 0 - credential.ticket = CountedOctetString() - credential.ticket['data'] = encoder.encode( - krbCred['tickets'][0].clone(tagSet=Ticket.tagSet, cloneValueFlag=True) - ) - credential.ticket['length'] = len(credential.ticket['data']) - credential.secondTicket = CountedOctetString() - credential.secondTicket['data'] = b'' - credential.secondTicket['length'] = 0 - - self.credentials.append(credential) + # Loop through all ticket-info entries + for i, krbCredInfo in enumerate(encKrbCredPart["ticket-info"]): + # Set the ccache principal from the first entry + if i == 0: + tmpPrincipal = types.Principal() + tmpPrincipal.from_asn1(krbCredInfo, "prealm", "pname") + self.principal = Principal() + self.principal.fromPrincipal(tmpPrincipal) + + credential = Credential() + server = types.Principal() + server.from_asn1(krbCredInfo, "srealm", "sname") + tmpServer = Principal() + tmpServer.fromPrincipal(server) + + credential["client"] = self.principal + credential["server"] = tmpServer + credential["is_skey"] = 0 + + credential["key"] = KeyBlockV4() + credential["key"]["keytype"] = int(krbCredInfo["key"]["keytype"]) + credential["key"]["keyvalue"] = krbCredInfo["key"]["keyvalue"].asOctets() + credential["key"]["keylen"] = len(credential["key"]["keyvalue"]) + + credential["time"] = Times() + + credential["time"]["authtime"] = self.toTimeStamp( + types.KerberosTime.from_asn1(krbCredInfo["starttime"]) + ) + credential["time"]["starttime"] = self.toTimeStamp( + types.KerberosTime.from_asn1(krbCredInfo["starttime"]) + ) + credential["time"]["endtime"] = self.toTimeStamp( + types.KerberosTime.from_asn1(krbCredInfo["endtime"]) + ) + # After KB4586793 for CVE-2020-17049 this timestamp may be omitted + if krbCredInfo["renew-till"].hasValue(): + credential["time"]["renew_till"] = self.toTimeStamp( + types.KerberosTime.from_asn1(krbCredInfo["renew-till"]) + ) + + flags = self.reverseFlags(krbCredInfo["flags"]) + credential["tktflags"] = flags + + credential["num_address"] = 0 + credential.ticket = CountedOctetString() + credential.ticket["data"] = encoder.encode( + krbCred["tickets"][i].clone(tagSet=Ticket.tagSet, cloneValueFlag=True) + ) + credential.ticket["length"] = len(credential.ticket["data"]) + credential.secondTicket = CountedOctetString() + credential.secondTicket["data"] = b"" + credential.secondTicket["length"] = 0 + + self.credentials.append(credential) def toKRBCRED(self): principal = self.principal - credential = self.credentials[0] - - krbCredInfo = KrbCredInfo() - - krbCredInfo['key'] = noValue - krbCredInfo['key']['keytype'] = credential['key']['keytype'] - krbCredInfo['key']['keyvalue'] = credential['key']['keyvalue'] - krbCredInfo['prealm'] = principal.realm.fields['data'] + krbCredInfos = [] + tickets = [] - krbCredInfo['pname'] = noValue - krbCredInfo['pname']['name-type'] = principal.header['name_type'] - seq_set_iter(krbCredInfo['pname'], 'name-string', (principal.components[0].fields['data'],)) - - krbCredInfo['flags'] = credential['tktflags'] - - krbCredInfo['starttime'] = KerberosTime.to_asn1(datetime.fromtimestamp(credential['time']['starttime'], tz=timezone.utc)) - krbCredInfo['endtime'] = KerberosTime.to_asn1(datetime.fromtimestamp(credential['time']['endtime'], tz=timezone.utc)) - krbCredInfo['renew-till'] = KerberosTime.to_asn1(datetime.fromtimestamp(credential['time']['renew_till'], tz=timezone.utc)) - - krbCredInfo['srealm'] = credential['server'].realm.fields['data'] - - krbCredInfo['sname'] = noValue - krbCredInfo['sname']['name-type'] = credential['server'].header['name_type'] - tmp_service_class = credential['server'].components[0].fields['data'] - tmp_service_hostname = credential['server'].components[1].fields['data'] - seq_set_iter(krbCredInfo['sname'], 'name-string', (tmp_service_class, tmp_service_hostname)) + for credential in self.credentials: + krbCredInfo = KrbCredInfo() + + krbCredInfo["key"] = noValue + krbCredInfo["key"]["keytype"] = credential["key"]["keytype"] + krbCredInfo["key"]["keyvalue"] = credential["key"]["keyvalue"] + + krbCredInfo["prealm"] = principal.realm.fields["data"] + + krbCredInfo["pname"] = noValue + krbCredInfo["pname"]["name-type"] = principal.header["name_type"] + seq_set_iter( + krbCredInfo["pname"], + "name-string", + tuple(c.fields["data"] for c in principal.components), + ) + + # Force the 32 bit representation to avoid any precision loss due to ASN1 conversion + krbCredInfo["flags"] = f"{int(credential['tktflags']):032b}" + + krbCredInfo["starttime"] = KerberosTime.to_asn1( + datetime.fromtimestamp(credential["time"]["starttime"], tz=timezone.utc) + ) + krbCredInfo["endtime"] = KerberosTime.to_asn1( + datetime.fromtimestamp(credential["time"]["endtime"], tz=timezone.utc) + ) + if credential["time"]["renew_till"] != 0: + krbCredInfo["renew-till"] = KerberosTime.to_asn1( + datetime.fromtimestamp( + credential["time"]["renew_till"], tz=timezone.utc + ) + ) + + krbCredInfo["srealm"] = credential["server"].realm.fields["data"] + + krbCredInfo["sname"] = noValue + krbCredInfo["sname"]["name-type"] = credential["server"].header["name_type"] + seq_set_iter( + krbCredInfo["sname"], + "name-string", + tuple(c.fields["data"] for c in credential["server"].components), + ) + + krbCredInfos.append(krbCredInfo) + + ticket = decoder.decode(credential.ticket["data"], asn1Spec=Ticket())[0] + tickets.append(ticket) encKrbCredPart = EncKrbCredPart() - seq_set_iter(encKrbCredPart, 'ticket-info', (krbCredInfo,)) + seq_set_iter(encKrbCredPart, "ticket-info", krbCredInfos) krbCred = KRB_CRED() - krbCred['pvno'] = 5 - krbCred['msg-type'] = 22 + krbCred["pvno"] = 5 + krbCred["msg-type"] = 22 - krbCred['enc-part'] = noValue - krbCred['enc-part']['etype'] = 0 - krbCred['enc-part']['cipher'] = encoder.encode(encKrbCredPart) + krbCred["enc-part"] = noValue + krbCred["enc-part"]["etype"] = 0 + krbCred["enc-part"]["cipher"] = encoder.encode(encKrbCredPart) - ticket = decoder.decode(credential.ticket['data'], asn1Spec=Ticket())[0] - seq_set_iter(krbCred, 'tickets', (ticket,)) + seq_set_iter(krbCred, "tickets", tickets) encodedKrbCred = encoder.encode(krbCred)