Active Directory environments are full of subtle misconfigurations that can lead to complete domain compromise. One of the less-documented attack paths combines two primitives that alone seem harmless: Constrained Delegation and WriteSPN. Together, they enable an attacker to impersonate any user, including Domain Admins, against a Domain Controller.
SPN Jacking is particularly valuable when classic alternatives like RBCD or Shadow Credentials are blocked or monitored. SPN Jacking offers an alternative path to domain compromise using only WriteSPN and an already-configured delegation.
This technique was first documented by Elad Shamir in 2022. This post covers both variants with a practical lab walkthrough.
Constrained Delegation allows a service account to request Kerberos tickets on behalf of other users, but only for specific target services. It is configured via the msDS-AllowedToDelegateTo attribute and relies on two Kerberos extensions:
S4U2Self – the account obtains a forwardable ticket for any user, for itself (Protocol Transition)
KDC will only issue a forwardable ticket via S4U2Self if the requesting account has the TrustedToAuthForDelegation flag set. Without it, the ticket is not forwardable and S4U2Proxy will reject it.
Delegator → KDC: “Administrator just connected to me, give me a ticket proving that.”
KDC: “Do you have TrustedToAuthForDelegation? Yes? Ok, I trust you. Here’s a ticket for Administrator → delegator.”
S4U2Proxy – the account uses that ticket to request a TGS for the target SPN
KDC will only accept S4U2Proxy if the SPN passed via -spn is listed in the msDS-AllowedToDelegateTo attribute of the requesting account.
Delegator → KDC: “I have Administrator’s ticket, now I want a ticket to http/WIN10.lab.local on his behalf.”
KDC: “Checking msDS-AllowedToDelegateTo… http/WIN10.lab.local is on the list? Yes? Ok, here’s the ticket.”
Together, these two checks form the delegation chain: TrustedToAuthForDelegation
allows the account to obtain a forwardable ticket without the user being present
(S4U2Self), and msDS-AllowedToDelegateTo controls which services that ticket
can be forwarded to (S4U2Proxy). Both must be satisfied, without the first,
the ticket isn’t forwardable; without the second, the KDC rejects the proxy request.
WriteSPN is a DACL permission that grants the ability to modify the servicePrincipalName attribute on an AD object. This is often granted carelessly during service account deployments.
The KDC encrypts each TGS ticket using the long-term key of the account that owns the target SPN. If an attacker moves a SPN onto a high-value account like a Domain Controller, the resulting TGS will be encrypted with that DC’s key. Combined with -altservice, this ticket becomes valid for CIFS, LDAP, or other services running on that DC, enabling DCSync and full domain compromise.
impacket-getST \
-spn 'http/WIN10.lab.local'\
-altservice 'cifs/DC01.lab.local'\
-impersonate 'Administrator'\
-dc-ip 192.168.10.30 \
'lab.local/delegator:Password123!'[-] CCache file is not found. Skipping...
[*] Getting TGT for user
[*] Impersonating Administrator
[*] Requesting S4U2self
[*] Requesting S4U2Proxy
[*] Changing service from http/[email protected] to cifs/[email protected][*] Saving ticket in Administrator@[email protected]
The -altservice flag substitutes the SPN in the ticket from http/WIN10.lab.local to cifs/DC01.lab.local. This works because both SPNs belong to DC01$ – the KDC encrypted the ticket with DC01’s key, so the substitution is valid.
In this variant we never touch an existing SPN. The delegation is configured to point at a SPN that does not exist or was just renamed to something else. This orphaned SPN exists in msDS-AllowedToDelegateTo on WIN10$, but no other object has it. Which means that we can take it and register it on DC01 or some other machine account that we have WriteSPN access to.
Step 1 – Finding GhostSPN configured on Delegator#
The KDC resolves a SPN lookup by finding which account owns that SPN in LDAP. When it issues a TGS for http/WIN10.lab.local, it encrypts the ticket with the long-term key of whichever account holds that SPN at query time.
By moving the SPN to DC01$, the ticket is now encrypted with DC01’s machine account key. The -altservice trick then rewrites the SPN name in the ticket body, this is permitted because the encryption key hasn’t changed. The result is a valid cifs/DC01.lab.local ticket for any user we chose to impersonate.