Skip to content

Referal ST fix#337

Open
0xlazY wants to merge 1 commit intoly4k:mainfrom
0xlazY:trust_fix
Open

Referal ST fix#337
0xlazY wants to merge 1 commit intoly4k:mainfrom
0xlazY:trust_fix

Conversation

@0xlazY
Copy link
Copy Markdown

@0xlazY 0xlazY commented Dec 1, 2025

I recently had trouble authenticating using cross-realm kerberos tickets. Certipy didn't manage to retreive a valid ST for a service on a different domain and was also unable to authenticate when supplied with a valid cross-realm ST (manually through KRB5CCNAME).

I think this issue is also mentioned here #328

This fixes both cases (and from my testing won't break any other, but this should be properly tested) :

  1. Full authentication chain with user provided password/AesKey or TGT
  • When calling getKerberosTGS, certipy populates the kdc_host value with the -dc-ip user supplied parameter or resolves the initial domain name to get a kdc_host value (ip address)
  • This forces getKerberosTGS to always query the same kdc, resulting in a KDC_ERR_WRONG_REALM error
    (Since this effectively means trying to acquire an ST for SPN/DomainB by asking DomainA KDC)
  • By using None for kdc_host, getKerberosTGS impacket function handles the kdc switching mechanism on it's own and the final ST is retrieved (just like getST.py would)
  1. User supplied ST
  • When directly using a valid ST for DomainB, acquired by a DomainA user, the domain variable is extracted from the decoded final ticket
# lib/kerberos.py l.934
# Step 4: Extract client information from ticket
ticket = decoder.decode(kdc_rep, asn1Spec=TGS_REP())[0]
client_name = Principal()
client_name = client_name.from_asn1(ticket, "crealm", "cname")

# Extract the username and domain from the client name
username = "@".join(str(client_name).split("@")[:-1])
domain = client_name.realm or "" # HERE

return kdc_rep, cast(type, cipher), cast(Key, session_key), username, domain
  • This value is incorrect since the realm attribute of the decoded ST is relative to DomainB and not the initial identity from DomainA user
  • Using the already stored domain variable (extracted from cli parameter or parsed on the .ccache) fixes the missmatch

I'm aware this requires DNS resolution to work but when doing cross-realm authentication, I think it's a fair requirement to avoid a lot of coding addition.

@ly4k
Copy link
Copy Markdown
Owner

ly4k commented Dec 1, 2025

Awesome! I currently don't have a multi forest lab at hand - do you know how this behaves with regards to DNS? Currently we have a DNS resolver, and I’m not sure how Impacket's auto-feature works - whether it's IP-based or DNS based when it gets referred and such

@0xlazY
Copy link
Copy Markdown
Author

0xlazY commented Dec 1, 2025

I currently don't have a multi forest lab at hand

You can just use a parent-child environment, that will give you the same error

getKerberosTGS is a recursive function: While the returned sname is different then the target sname (in our case since the first call to getKerberosTGS retreives a referal ticket -> krbtgt/DomainB != SPN/DomainB) it updates the domain variable from the 2nd sname-string attribut (here DomainB).

# krb5/kerberosv5.py l.476
res = decoder.decode(r, asn1Spec = TGS_REP())[0]
spn = Principal()
spn.from_asn1(res['ticket'], 'realm', 'sname')

    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)

For DNS resulution, it doesn't seem to have any internal resolver, just using socket.getaddrinfo on the extracted domain value

# krb5/kerberosv5.py l.53
def sendReceive(data, host, kdcHost, port=88):
    # [...]
    # targetHost is either domain or user supplied -dc-ip <ip>
    af, socktype, proto, canonname, sa = socket.getaddrinfo(targetHost, port, 0, socket.SOCK_STREAM)[0]

This recursion could be implemented on Certipy to preserve the use of the internal DNS resolver (and is the only way I guess ?). But that would mean rewriting getKerberosTGS, unless I'm missing development knowledge 😢

@0xlazY
Copy link
Copy Markdown
Author

0xlazY commented Feb 11, 2026

Any news on this one ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants