Skip to content

Check cached TGT matches requested user in getST.py - #2218

Open
p-nowodzinski wants to merge 2 commits into
fortra:masterfrom
p-nowodzinski:impacket-getst-cache-principal-check
Open

Check cached TGT matches requested user in getST.py#2218
p-nowodzinski wants to merge 2 commits into
fortra:masterfrom
p-nowodzinski:impacket-getst-cache-principal-check

Conversation

@p-nowodzinski

Copy link
Copy Markdown
Contributor

Cached Kerberos TGTs are only reused when they belong to the same principal requested via the CLI.

Previously, getST.py reused a cached TGT based only on realm/domain resolution from CCache.parseFile(), without validating that the cached credential belonged to the requested username.

This could result in incorrect credential reuse when multiple principals exist within the same realm.

This change adds a principal equality check using impacket.krb5.types.Principal, ensuring both username and realm match before reusing a cached TGT.

  • Add principal-level validation for cached TGT reuse in getST.py
  • Invalidate cached TGT when username does not match requested principal
  • Fall back to requesting a new TGT when mismatch is detected

@anadrianmanrique anadrianmanrique added the bug Unexpected problem or unintended behavior label Jul 1, 2026
@gabrielg5

gabrielg5 commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

Hey hello @p-nowodzinski,

Been reviewing this PR and as is, it's failing with KeyError: 'client' because Credential.toTGT() is only returning KDC_REP, cipher and sessionKey. It does not include client.

So we should fix it. I see a few options:

  • use the username already returned by CCache.parseFile() when it loads the cached credential (guess this option is the best one here)
  • decode crealm/cname from TGT['KDC_REP'] in getST.py
  • add the client principal to the dict returned by Credential.toTGT()

Another item here, currently (after fixing the KeyError) we will be comparing Principal objects. That could be a bit too strict in terms of casing. Perhaps we can compare user and domain case-insensitively, without changing Principal.__eq__ globally.

Thank you!

@gabrielg5 gabrielg5 added the waiting for response Further information is needed from people who opened the issue or pull request label Jul 8, 2026
@p-nowodzinski

Copy link
Copy Markdown
Contributor Author

Thanks for the review and for pointing out the KeyError with Credential.toTGT()

I've updated the change to use the username returned by CCache.parseFile() instead of accessing the cached credential directly, and switched the comparison to be case-insensitive.

I've just added username validation as it looks like the CCache.parseFile() method already filters the cached TGT by the requested realm. I have added the cached realm in the warning message for completeness, although the check in parseFile() should prevent a realm mismatch from happening.

@gabrielg5 gabrielg5 removed the waiting for response Further information is needed from people who opened the issue or pull request label Jul 24, 2026

@gabrielg5 gabrielg5 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey hello!
Thanks for those fixes... after changing a bit more I found another cross-realm scenario we are not catching here

Comment thread examples/getST.py
tgt, cipher, sessionKey = TGT['KDC_REP'], TGT['cipher'], TGT['sessionKey']
oldSessionKey = sessionKey

if self.__user.lower() != username.lower():

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There may still be an issue with this comparison. I tested current branch state against GOAD-Light and the cached realm is still not validated

Using a TGT with:

  • client: hodor@NORTH.SEVENKINGDOMS.LOCAL
  • server: krbtgt/SEVENKINGDOMS.LOCAL@NORTH.SEVENKINGDOMS.LOCAL

I ran getST.py with the identity sevenkingdoms.local/hodor. The cache was accepted without a warning, and DC issued a service ticket whose client remained hodor@NORTH.SEVENKINGDOMS.LOCAL.

It appears parseFile() returns the requested domain rather than the cached client’s realm, so comparing only the returned username does not catch cross-realm principal mismatches.

@gabrielg5 gabrielg5 added the waiting for response Further information is needed from people who opened the issue or pull request label Jul 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Unexpected problem or unintended behavior waiting for response Further information is needed from people who opened the issue or pull request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants