SPN-Jacking
SPN-Jacking
So i've been going through kerberos delegation attacks lately and one thing that really caught my attention was SPN-Jacking. Its a pretty intresting technique and once you understand how delegation works in AD, it all starts to make alot of sense. In this post im gonna walk through what SPN-Jacking actually is and then cover the three main types of it.
What is SPN-Jacking?
Before we jump in, lets quickly recap whats going on here. In Active Directory, Constrained Delegation allows a service account or computer to request kerberos tickets on behalf of other users, but only to specific SPNs (Service Principal Names) that are configured by the admin.
An SPN is basically just a unique identifier that points to a service running on a specific machine. Something like cifs/ServerB means the CIFS service on ServerB.
Now here's the thing, when a server like ServerA is configured for constrained delegation, it has a list of SPNs it is allowed to delegate to. The attack idea behind SPN-Jacking is simple: what if the attacker can get one of those target SPNs associated with a machine they control? Then ServerA would basically be delegating tickets to the attacker's machine without realizing it.
Thats the core idea. Now lets look at the three different ways this can play out.
Ghost SPN-Jacking
This is the simplest and cleanest version of the attack.
Imagine ServerA is configured for constrained delegation to the SPN cifs/OldServer. But OldServer doesn't exist anymore, maybe the computer account got deleted, or the machine got renamed and the SPNs were updated, so that particular SPN is now just floating out there with no account attached to it.
Since nobody owns that SPN, the attacker can just go ahead and add it to ServerC (a machine they control). Now when the S4U attack runs using ServerA's account, it requests a service ticket for a privileged user to cifs/OldServer, but that ticket is actually encrypted for ServerC since ServerC now has that SPN.
The service name inside the ticket won't match ServerC's hostname, but thats fine because the service name is not part of the encrypted portion of the ticket. So the attacker can just swap it out for a valid SPN on ServerC, and then use the ticket to compromise ServerC.
Pretty clean right? No conflict, no drama, just a dangling SPN waiting to be claimed.
Attack Steps
Find SPNs in the KCD configuration of ServerA
Confirm the target SPN has no active account associated with it
Add the orphaned SPN to ServerC (attacker controlled machine)
Run the S4U2self + S4U2proxy chain using ServerA's credentials to get an impersonating ticket
Edit the ticket's SPN to a valid one on ServerC
Pass the ticket and get access to ServerC
Commands (from Linux)
Quick note on editing the sname: you got two ways to do it. Either you just throw
-altservicedirectly in thegetST.pycommand and it handles everything in one go, or you rungetST.pyfirst to get the ticket and then usetgssub.pyseperately to swap the service name. Both do the same thing, just depends on what you prefer.
Live SPN-Jacking
This one is a bit more involved but still very doable.
Here ServerA is configured for constrained delegation to cifs/ServerB, and ServerB does exist and actively owns that SPN. The attacker has WriteSPN rights on both ServerB and ServerC.
The problem is, in a fully patched environment, domain controllers won't let you have the same SPN registered on two different accounts at the same time. So if the attacker just tries to add cifs/ServerB to ServerC, the DC will reject it because ServerB already has it.
The workaround is a quick two step move. First, remove the SPN from ServerB. Then add it to ServerC. Now the DC is happy because there's no conflict. The attacker then runs the full S4U attack using ServerA, gets the impersonating ticket encrypted for ServerC, edits the service name, and uses it to compromise ServerC.
After the attack, a responsible attacker would roll back the changes, removing the SPN from ServerC and restoring it back to ServerB so nothing looks broken.
Attack Steps
Confirm WriteSPN rights on both ServerB and ServerC
Remove the target SPN from ServerB temporarily
Add that SPN to ServerC
Run S4U2self + S4U2proxy to get the impersonating ticket
Edit the SPN in the ticket
Pass the ticket to access ServerC
Rollback: remove SPN from ServerC and restore it to ServerB
Commands (from Linux)
Same deal here for editing the sname, either pass
-altservicedirectly togetST.pyand its done in one step, or grab the ticket first and then runtgssub.pyto patch the service name. Both work fine.
SPN-Jacking With the HOST Service Class
This is where things get really interesting. Bear with me here.
By default every computer account in AD has SPNs registered for the HOST service class, something like HOST/ServerB. Now the HOST service class is actually a catch-all that maps to a huge list of other service classes internaly, including things like cifs, http, spooler, rpc, dns and many more.
So what happens if the constrained delegation SPN on ServerA is pointing to cifs/ServerB, but cifs is not explicitly registered as an SPN on ServerB, its just covered by the HOST mapping?
When the attacker tries to add cifs/ServerB directly to ServerC, the DC rejects it because it knows cifs is mapped under HOST, and HOST is already on ServerB.
The trick here is:
First remove the HOST SPNs from ServerB
Then explicitly add
cifs/ServerBto ServerCNow here's the cool part: add the HOST SPNs back to ServerB
The DC doesnt complain when you put HOST back on ServerB even though cifs/ServerB is now on ServerC. Thats the weird behavior being exploited here.
After this, when any client or the DC tries to resolve cifs/ServerB, it gets the ticket encrypted for ServerC instead of ServerB. The rest of the attack is the same, edit the ticket SPN and pass it to compromise ServerC.
Attack Steps
Identify that the target SPN is covered by HOST mapping (not explicitly set)
Remove HOST SPNs from ServerB
Explicitly add the target SPN (eg.
cifs/ServerB) to ServerCAdd HOST SPNs back to ServerB (DC wont complain)
Run S4U attack using ServerA to get impersonating ticket encrypted for ServerC
Edit the ticket SPN
Pass the ticket to access ServerC
Commands (from Linux)
So for editing the sname here its the same as the other types, you can either do
-altservicedirectly ingetST.pywhich does everything in one go, or you get the ticket first and then usetgssub.pyto swap the service name out. Pick whatever fits your flow.
References:
Last updated