Internal Network & Active Directory · intermediate · ~10 min

LDAP and domain enumeration

**What you will learn** - Explain how Active Directory (AD) is queried over LDAP on ports 389/636 and why any authenticated domain user can read most of the directory. - Build safe, read-only `ldapsearch` filters to enumerate users, groups, computers, and account flags in an authorized lab. - Recognise the high-value findings enumeration exposes: Service Principal Names (SPNs), disabled Kerberos pre-authentication, nested-group effective admins, and secrets leaked into attributes like `description`. - Draw a threat model of AD's read-access trust boundary and decide where enumeration data actually comes from. - Detect enumeration defensively: which Windows events and LDAP logs reveal bulk directory reads, and what to log versus never log. - Remediate the leaks enumeration finds and verify the fix rejects the bad state and accepts the good state.

Overview

Security objective. The asset you are protecting is the Active Directory directory itself — the authoritative inventory of every user, group, computer, and policy in an organization. The threat is reconnaissance: an attacker (or a red-teamer with written authorization) who holds one ordinary domain account and uses it to read the whole directory, then picks their next attack from that map. In this lesson you will learn to perform that read-only enumeration in a lab, and — just as importantly — to detect it and to close the specific leaks it finds.

What LDAP is. Active Directory is, underneath, an LDAP directory. LDAP (Lightweight Directory Access Protocol) is the query language and wire protocol clients use to search and read AD. Domain controllers listen on 389/tcp (plain or STARTTLS) and 636/tcp (LDAPS, TLS-wrapped). The Global Catalog answers on 3268/3269.

The uncomfortable default. In a standard AD deployment, any authenticated domain user — not just admins — can read most objects and most attributes. That is by design: applications, logon scripts, address books, and the OS itself all depend on broad directory reads. The consequence is that a single low-privilege login yields a complete, authoritative map of the environment: who the admins really are (after group nesting is resolved), which accounts are Kerberoastable, which have pre-auth disabled, which are stale, and which have passwords sitting in a comment field.

How this builds on your prereq. You already met AD objects: users, groups, computers, and OUs. This lesson is the query layer on top of those objects — it teaches how those same objects are read over the wire, and which of their attributes turn into attack surface. The next lesson, credential attacks (spraying and roasting), consumes exactly the target list you produce here.

Why it matters

In authorized internal penetration tests and red-team engagements, LDAP enumeration is almost always the first substantive step after gaining any foothold credential. It is the highest-yield, lowest-noise reconnaissance available: one ordinary account, no exploit, no privilege escalation, reveals the entire attack surface.

For defenders — the larger and more durable career path — the same knowledge is what lets you answer "could someone map our whole domain from a single helpdesk account?" The answer is usually yes, so the professional work is reducing what that map reveals (attribute hygiene, tiering, least privilege) and detecting the reconnaissance itself (LDAP query logging, honey objects, baselines). A blue-teamer who has never watched enumeration happen cannot recognise it in the logs; a pentester who cannot explain the remediation writes a useless report.

Everything downstream depends on this step. Kerberoasting picks its targets from the SPN list you enumerate. Password spraying picks accounts and respects lockout thresholds you read from the domain policy. Attack-path tools like BloodHound are, at heart, industrial-scale LDAP enumerators plus a graph engine. Get enumeration right and the rest of the engagement is informed; skip it and you are guessing.

Core concepts

1. LDAP is how you read Active Directory

Definition. LDAP is a protocol for querying a hierarchical directory of entries, each identified by a Distinguished Name (DN) such as CN=Alice,OU=Staff,DC=lab,DC=local.

Plain explanation. Think of AD as a giant read-mostly database of objects arranged in a tree. LDAP is the query language and network protocol you use to search that tree and pull back attributes.

How it works. A client binds (authenticates) to a domain controller on 389 or 636, then issues a search specifying three things: a base DN (where in the tree to start), a scope (this object / one level / the whole subtree), and a filter (which objects match). The DC returns matching entries and the attributes you asked for.

When / when-not. Enumeration over LDAP is appropriate on any engagement where you have a valid credential and written authorization. It is not appropriate against systems you do not own or lack explicit permission to test — that is unauthorized access, full stop.

Pitfall. Beginners think they need admin rights. They do not. The whole point is that a normal user account is enough, which is exactly why this is a defensive priority.

2. Broad read access is the default trust boundary

Definition. By default, the built-in Authenticated Users / Pre-Windows 2000 Compatible Access configuration grants read on most of the directory to any domain member.

Plain explanation. The trust boundary in AD is not "admin vs. non-admin" for reads — it is "authenticated vs. anonymous." Once you are any authenticated user, you are inside the read boundary.

How it works. Applications legitimately need to resolve group membership, find printers, look up email addresses, and run logon scripts, so Microsoft ships broad read ACLs on directory objects. Tightening them breaks applications, which is why you cannot simply "turn reads off."

When / when-not. Some sensitive attributes (like ms-Mcs-AdmPwd, the LAPS local-admin password) are not world-readable and are protected by their own ACLs — enumerating those requires delegated rights. Do not assume every attribute is readable.

Pitfall. Assuming "low privilege" means "low visibility." In AD, low privilege still means near-total read visibility.

3. What enumeration yields (the attack surface)

Definition. The set of high-value facts a directory read exposes.

Plain explanation. You are not attacking yet — you are reading an authoritative map. From it you can pick targets without touching them.

How it works — the findings that matter:

Finding LDAP signal Why it matters
Kerberoastable account servicePrincipalName is set on a user object Its service ticket can be requested and cracked offline
AS-REP-roastable account userAccountControl has the DONT_REQ_PREAUTH bit (0x400000) Its AS-REP can be requested with no password and cracked offline
Effective admin Nested memberOf chains resolving into Domain Admins / privileged groups Reveals who is really an admin, not just direct members
Stale / risky account pwdLastSet, lastLogonTimestamp, userAccountControl flags (never-expires, disabled) Old, forgotten, over-privileged accounts are easy wins
Leaked secret Cleartext password in description, info, or comment fields Instant credential with zero cracking

When / when-not. You enumerate broadly first, then narrow. Do not fixate on one object type; the value is in cross-referencing (e.g., an SPN account that is also a member of a privileged group).

Pitfall. Treating enumeration output as an attack. Reading is not exploitation — but acting on what you read (roasting, spraying) is, and requires the same authorization scope.

4. Tooling (conceptual)

Definition. The clients that speak LDAP to AD.

Plain explanation. ldapsearch (the classic OpenLDAP CLI) is the transparent, teachable one — you write the filter yourself. PowerShell's Get-ADUser/Get-ADObject cmdlets wrap the same queries on Windows. Purpose-built tools and BloodHound ingest the same data and compute attack paths automatically.

How it works. All of them do the same thing under the hood: bind, search, read attributes. Only the ergonomics differ.

Pitfall. Believing a fancy tool sees something ldapsearch cannot. It generally does not — it just correlates faster. Learning the raw filter makes you tool-independent and a better defender, because you understand what the logs will show.

5. Defense: you cannot block reads, so trim and watch

Definition. The two realistic controls: attribute hygiene and query monitoring.

Plain explanation. Because reads are load-bearing for the whole environment, defense is not "deny" — it is "reduce what a read reveals" and "notice when someone reads everything."

How it works. Attribute hygiene means keeping secrets out of readable fields, minimising SPNs on user accounts, disabling DONT_REQ_PREAUTH, and cleaning up stale privileged accounts. Monitoring means enabling directory-service and LDAP query logging, baselining normal query volume, and alerting on bulk or unusual searches — optionally seeding honey objects that no legitimate app should ever query.

Pitfall. Trusting a vulnerability scanner's "pass" as proof of security. A clean scan does not mean the directory is safe to enumerate — scanners rarely model "one normal user can map everything." Nothing here is ever "completely secure"; you reduce and detect.

AD LDAP ENUMERATION — THREAT MODEL (authorized lab)

  ASSET: the AD directory (users, groups, computers, policy, secrets-in-attributes)

  +-------------------+        389/636 LDAP bind + search        +----------------------+
  |  Any AUTHENTICATED| --------------------------------------> |  Domain Controller   |
  |  domain user      |   (base DN, scope, filter, attrs)       |  (LDAP service)      |
  |  (foothold cred)  | <-------------------------------------- |  Directory database  |
  +-------------------+        matching entries + attributes     +----------------------+
         |                                                                 |
         | ENTRY POINT: valid credential (phished, sprayed, reused)        |
         v                                                                 v
  ===================  TRUST BOUNDARY  ==============================================
  Outside: anonymous / unauthenticated  |  Inside: authenticated read (near-total)
  ==================================================================================

  INSECURE ASSUMPTION: "low privilege = low visibility"  (FALSE in default AD)
  DETECTION SURFACE: DC Directory-Service log, LDAP query events (1644),
                     logon events (4624), volume/pattern baselines, honey objects

Knowledge check

  1. What asset is protected here, and what single credential is enough to read most of it? (The AD directory; any one authenticated domain user account.)
  2. Where is the trust boundary for directory reads — is it admin-vs-user or authenticated-vs-anonymous? (Authenticated-vs-anonymous; being any authenticated user puts you inside the read boundary.)
  3. Which log sources would let a defender notice bulk enumeration, and why can't the defender simply forbid the reads? (Directory-Service / LDAP query logging, event 1644, logon 4624, plus volume baselines; reads cannot be blocked because applications and the OS depend on them.)

Syntax notes

The core operation is an LDAP search: bind to a DC, then specify base DN, scope, filter, and the attributes to return. With ldapsearch:

ldapsearch -x \                       # simple bind (use -Y GSSAPI for Kerberos)
  -H ldap://dc01.lab.local \          # the domain controller (lab host only)
  -D 'LAB\svc-reader' \               # bind DN / user (a normal low-priv account)
  -w '<lab-placeholder-password>' \   # never a real secret; lab placeholder
  -b 'DC=lab,DC=local' \              # BASE DN: where in the tree to start
  -s sub \                            # SCOPE: sub = whole subtree
  '(objectClass=user)' \              # FILTER: which objects match
  sAMAccountName memberOf            # ATTRIBUTES to return (omit = all readable)

Key filter building blocks (LDAP filters are prefix/Polish notation, parenthesised):

Goal Filter
All person users (&(objectCategory=person)(objectClass=user))
Accounts with an SPN (Kerberoastable) (&(objectClass=user)(servicePrincipalName=*))
Pre-auth disabled (AS-REP-roastable) (userAccountControl:1.2.840.113556.1.4.803:=4194304)
Password-never-expires (userAccountControl:1.2.840.113556.1.4.803:=65536)
Disabled accounts (userAccountControl:1.2.840.113556.1.4.803:=2)

1.2.840.113556.1.4.803 is the bitwise-AND matching rule OID — it tests a single flag inside the userAccountControl bitmask. 4194304 = 0x400000 = DONT_REQ_PREAUTH.

Lesson

LDAP (Lightweight Directory Access Protocol, ports 389/636) is how clients query Active Directory.

The key point: any authenticated domain user can read most of the directory. So a single low-privilege account exposes an enormous amount of information.

What you can enumerate

  • All users and their attributes: groups, SPNs, account flags, and the description field.
  • All groups and their membership — including nesting, which reveals who is effectively a Domain Admin.
  • All computers, their OS versions, and which ones are domain controllers.
  • GPOs, trusts, password policy, and Kerberos settings.

Why it is so powerful

You are not attacking yet — you are reading a complete, authoritative map of the organization with one normal account.

From that map you can identify:

  • Kerberoastable accounts (those with SPNs).
  • AS-REP-roastable accounts (those with pre-authentication disabled).
  • Over-privileged and stale admin accounts.
  • Credential leaks hidden in object attributes.

Tools (conceptual)

ldapsearch, the PowerShell AD cmdlets, and purpose-built tools all enumerate this data.

BloodHound (covered a couple of lessons from now) ingests it and computes attack paths automatically.

Defensive note

Read access is broad by default, so you cannot simply forbid directory reads. The realistic defenses are:

  • Monitoring for unusual LDAP queries.
  • Reducing exposed attributes — for example, clearing passwords out of the description field.

Code examples

The shape below is INSECURE (what enumeration finds) -> SECURE (the remediation) -> VERIFY (proof the fix rejects the bad state and accepts the good state). Run every command only against a domain you own or are authorized to test — localhost, a container, or an intentionally vulnerable lab such as GOAD (Game of Active Directory).

(1) INSECURE — intentionally vulnerable

WARNING: intentionally vulnerable — use only in a local, isolated, authorized lab. Do not deploy.

A classic leak: an admin stored a service password in the description field, thinking it was harmless. Any authenticated user can read it.

# LAB ONLY. Read the description attribute of every user in one search.
ldapsearch -x -H ldap://dc01.lab.local \
  -D 'LAB\svc-reader' -w '<lab-placeholder-password>' \
  -b 'DC=lab,DC=local' -s sub \
  '(&(objectCategory=person)(objectClass=user)(description=*))' \
  sAMAccountName description

# Example (fabricated lab) output — note the leaked secret:
#   sAMAccountName: svc-backup
#   description: backup service - pw Winter2026! do not change

The same one-account read also exposes roastable targets:

# LAB ONLY. Enumerate SPN (Kerberoastable) and pre-auth-disabled accounts.
ldapsearch -x -H ldap://dc01.lab.local \
  -D 'LAB\svc-reader' -w '<lab-placeholder-password>' \
  -b 'DC=lab,DC=local' -s sub \
  '(&(objectClass=user)(servicePrincipalName=*))' sAMAccountName servicePrincipalName

ldapsearch -x -H ldap://dc01.lab.local \
  -D 'LAB\svc-reader' -w '<lab-placeholder-password>' \
  -b 'DC=lab,DC=local' -s sub \
  '(userAccountControl:1.2.840.113556.1.4.803:=4194304)' sAMAccountName

(2) SECURE — remediate the leaks

Defense is not blocking the read; it is removing what the read reveals. Run these as an authorized admin in the lab.

# 1. Purge the secret from the readable attribute.
Set-ADUser -Identity svc-backup -Clear description

# 2. Rotate the exposed credential (it is now compromised) and store it in a vault,
#    never in a directory attribute.
#    (Password rotation is done through your vault / IdM, not shown here.)

# 3. Re-enable Kerberos pre-authentication (clear the DONT_REQ_PREAUTH bit).
Set-ADAccountControl -Identity someUser -DoesNotRequirePreAuth $false

# 4. Remove SPNs from ordinary user accounts that do not need them;
#    prefer Group Managed Service Accounts (gMSA) with long random secrets.
Set-ADUser -Identity svc-legacy -ServicePrincipalNames @{Remove='HTTP/app.lab.local'}

And turn on detection so the next enumeration is seen:

# Enable expensive/inefficient LDAP query logging on the DC (writes event 1644).
# 15-minute threshold, log searches that are slow or return many objects.
reg add "HKLM\SYSTEM\CurrentControlSet\Services\NTDS\Diagnostics" `
  /v "15 Field Engineering" /t REG_DWORD /d 5 /f

(3) VERIFY — prove the fix rejects bad and accepts good

# REJECTS bad state: the leaked-secret search must now return NO entries.
ldapsearch -x -H ldap://dc01.lab.local \
  -D 'LAB\svc-reader' -w '<lab-placeholder-password>' \
  -b 'DC=lab,DC=local' -s sub \
  '(&(objectClass=user)(description=*Winter2026*))' sAMAccountName
# Expected: '# numEntries: 0' (or no result line) -> secret is gone.

# REJECTS bad state: no user should still be AS-REP-roastable.
ldapsearch -x -H ldap://dc01.lab.local \
  -D 'LAB\svc-reader' -w '<lab-placeholder-password>' \
  -b 'DC=lab,DC=local' -s sub \
  '(userAccountControl:1.2.840.113556.1.4.803:=4194304)' sAMAccountName
# Expected: zero entries -> pre-auth is required again.

# ACCEPTS good state: legitimate lookups still work (no functionality broken).
ldapsearch -x -H ldap://dc01.lab.local \
  -D 'LAB\svc-reader' -w '<lab-placeholder-password>' \
  -b 'DC=lab,DC=local' -s sub \
  '(sAMAccountName=svc-backup)' sAMAccountName memberOf
# Expected: the account still resolves; only the leaked description is gone.

Then confirm detection fired: on the DC, check the Directory Service event log for event ID 1644 entries recording the bulk searches you just ran. If they are absent, logging is not configured and enumeration would go unseen.

Expected outcome. After remediation, the two "bad state" searches return zero entries while the normal lookup still succeeds — that is the definition of a verified fix: it rejects the insecure condition and accepts legitimate use.

Line by line

Walking the core ldapsearch invocation and what each part does:

Step Token What happens
1 -x Use a simple bind (username + password). In a real lab prefer -Y GSSAPI for Kerberos so no password crosses the wire.
2 -H ldap://dc01.lab.local Open a connection to the domain controller's LDAP service on 389. Use ldaps://...:636 for TLS.
3 -D 'LAB\svc-reader' Bind as a normal, low-privilege account — the whole point is that this is enough to read.
4 -w '<lab-placeholder-password>' The bind password. Never a real secret in teaching material; a placeholder here.
5 -b 'DC=lab,DC=local' Base DN: start the search at the domain root so the whole tree is in scope.
6 -s sub Scope sub = search the entire subtree beneath the base. (base = just that object, one = one level.)
7 '(&(objectCategory=person)(objectClass=user))' The filter. & is logical AND; this matches objects that are both person-category and user-class, excluding computer accounts.
8 sAMAccountName description The attribute list — return only these fields. Omitting it returns every readable attribute (noisier, but that is how leaks in odd fields get found).

How values change as you tighten the filter. Start with (objectClass=user) and you get every user. Add (servicePrincipalName=*) and the result set shrinks to only accounts that carry an SPN — your Kerberoast candidate list. Swap in the bitwise rule (...803:=4194304) and the DC evaluates userAccountControl AND 0x400000: any account whose flag bit is set matches, giving the AS-REP-roastable list. Each refinement is a different slice of the same one authenticated read — the DC does the filtering, so the network cost is small and the privilege cost is zero.

Why the bitwise OID matters. userAccountControl is a single integer whose bits each mean something (disabled, password-never-expires, pre-auth-not-required, ...). The matching-rule OID 1.2.840.113556.1.4.803 tells the DC "AND my constant against that integer and match if non-zero," which is the only way to test one flag without pulling and decoding every account client-side.

Common mistakes

Mistake 1 — assuming you need Domain Admin to enumerate.

  • Wrong approach: waiting to escalate privileges before mapping the domain.
  • Why wrong: in default AD, any authenticated user already reads almost everything; escalating first wastes time and adds noise.
  • Corrected: enumerate immediately with the foothold account, then use the map to plan escalation.
  • Recognise/prevent: if you are "stuck" with a low-priv account, that account is already enough to enumerate.

Mistake 2 — treating enumeration output as an attack you can run anywhere.

  • Wrong approach: pointing ldapsearch at a domain you found on the internet or a client system outside the agreed scope.
  • Why wrong: that is unauthorized access — illegal and unethical regardless of how "passive" it feels.
  • Corrected: run only against systems you own or that a signed authorization explicitly covers; keep the scope document handy.
  • Recognise/prevent: if you cannot name the written authorization that permits a target, do not touch it.

Mistake 3 — confusing reading with verifying.

  • Wrong approach: decoding an object attribute or a token and declaring the account "safe/valid." A related classic: decoding a JWT's payload and calling it verified.
  • Why wrong: reading a value proves nothing about its integrity or the account's protection; decoding a JWT is not verifying its signature.
  • Corrected: separate "I can read X" from "X is authentic/secure"; verification requires a cryptographic or policy check, not a read.
  • Recognise/prevent: ask "what check actually validated this?" — if the answer is "I could see it," that is not validation.

Mistake 4 — believing a clean scanner or a tightened firewall means the domain is safe.

  • Wrong approach: "the vuln scanner passed, enumeration isn't a risk."
  • Why wrong: scanners rarely model "one normal user maps everything"; passing a scan does not prove a system is secure, and nothing is ever completely secure.
  • Corrected: treat enumeration exposure as a design property (broad read + leaky attributes), and remediate + monitor it directly.
  • Recognise/prevent: test it yourself with a low-priv account in the lab and see what falls out.

Debugging tips

Common failures and how to work through them:

  • ldap_bind: Invalid credentials (49) — the bind DN or password is wrong, or the account is locked. Confirm the DOMAIN\user form (or full UPN user@lab.local), and check the account is enabled. In a lab, a wrong password can lock the account — mind the lockout policy you enumerated.
  • Operations error (1) on a plain bind — some DCs require signing or channel binding and reject simple binds over 389. Switch to ldaps://host:636 or Kerberos (-Y GSSAPI).
  • Zero results when you expect many — check the base DN. DC=lab,DC=local must match the actual domain; a typo returns nothing. Also confirm scope is sub, not base.
  • Filter returns everything / syntax error — LDAP filters are prefix notation and every clause is parenthesised. (&(a=1)(b=2)) is correct; (a=1 & b=2) is not. Balance your parentheses.
  • Bitwise flag filter matches nothing — verify the decimal constant. 4194304 = 0x400000 (DONT_REQ_PREAUTH), 65536 = password-never-expires, 2 = disabled. A wrong constant tests the wrong bit.
  • TLS/cert errors on 636 — the lab DC's certificate is untrusted by your client. For a lab only, LDAPTLS_REQCERT=never ldapsearch ... bypasses validation; never do this against production.

Questions to ask when it fails: Am I authenticated (bind succeeded) or just connected? Is my base DN the real domain root? Is my scope subtree? Is my filter valid prefix notation with balanced parens? Is the attribute I want actually world-readable, or is it ACL-protected (e.g., LAPS)?

Memory safety

Security & safety — detection and logging for enumeration.

Because you cannot block directory reads, detection is the primary control. Configure the domain controller to record LDAP activity and build a baseline of normal query volume so bulk enumeration stands out.

What to log (for each notable directory query / bind):

Field Example Why
Timestamp (UTC) 2026-07-07T14:02:11Z Order events, correlate across hosts
Source identity LAB\svc-reader + source IP/host Who and from where
Resource / scope base DN, filter, attributes requested Distinguishes a targeted lookup from a whole-tree sweep
Result entries returned, duration High counts / slow expensive searches signal enumeration
Security decision bind allowed/denied, signing enforced Shows the control that fired
Correlation ID session/request GUID Stitch a burst of searches into one campaign

Concrete signals in Windows: enable Field Engineering diagnostic level 5 on the DC (writes event 1644 for expensive/inefficient searches), correlate with logon event 4624 for the source account, and watch for a single account issuing whole-subtree searches for servicePrincipalName=*, userAccountControl bit filters, or reading description/memberOf across the entire tree in a short window.

What to NEVER log: the passwords or secrets you find in attributes (log "secret present in description of ", never the value), Kerberos keys or password hashes, session tokens or cookies, private keys, full PANs, or unneeded PII. Logging the leaked secret just moves it into a second readable place.

Which events signal abuse: a low-privilege or service account suddenly performing broad, high-count subtree searches; queries filtering specifically on roastable attributes; reads of honey objects (decoy accounts no real app should ever touch); enumeration bursts immediately after a new/first-seen logon.

How false positives arise: legitimate tools also enumerate broadly — vulnerability scanners, asset-inventory agents, identity-governance sync jobs, email address-book lookups, and even BloodHound run by your own blue team. Baseline these known sources and source IPs first, or you will drown real alerts in normal directory traffic. Tune on volume, timing, and which attributes, not on the mere fact that a read happened.

Real-world uses

Authorized real-world use case. During a scoped internal penetration test, a consultant is given a single standard domain user (simulating a phished employee). Their first move is read-only LDAP enumeration: user and group inventory, effective admins via nested groups, SPN and pre-auth-disabled lists, stale privileged accounts, and a sweep of description/info fields for leaked secrets. The deliverable is not "we got in" but a prioritised map that drives the rest of the engagement and, more importantly, a remediation list for the client. On the blue-team side, the exact same technique is used to audit one's own domain — "what can a helpdesk account see?" — and to validate that LDAP query logging actually captures the sweep.

Professional best-practice habits

Habit Beginner Advanced
Authorization Keep the signed scope open; never touch a host it doesn't cover Track scope per-subnet/OU; pause and re-confirm before touching new trusts
Least privilege Use the lowest-priv account that works Model tiered admin; verify reads that should be blocked (LAPS) truly are
Validation vs. reading Never call a decoded value "verified" Cryptographically verify tokens/signatures separately from reads
Secure defaults Remove secrets from readable attributes Enforce gMSA for services, drop unneeded SPNs, require LDAP signing/channel binding
Logging Enable event 1644 in the lab and watch your own queries appear Baseline query volume, deploy honey objects, alert on roastable-attribute sweeps
Error handling Read ldapsearch result codes instead of guessing Handle referrals, paging (>1000 results), and TLS/signing requirements cleanly

Authorization checklist (before any lab or engagement enumeration):

  1. Do I have written authorization naming these hosts/domain?
  2. Is the account I am using in scope, and am I within the agreed time window?
  3. Am I running against localhost / a container / an intentionally vulnerable VM / CTF only?
  4. Is my activity logged on my side (commands, timestamps) for the report?
  5. Do I have a cleanup/reset plan for anything I change?

Practice tasks

All tasks are lab-only: run them against a domain you own or an intentionally vulnerable AD lab (e.g., GOAD) on localhost/containers/an isolated VM. Confirm the authorization checklist first. Do not attack third-party systems.

Beginner 1 — Inventory users read-only.

  • Objective: enumerate all person users with a low-privilege account.
  • Requirements: one ldapsearch returning sAMAccountName and memberOf for (&(objectCategory=person)(objectClass=user)).
  • Output: a list of accounts and their direct groups.
  • Constraints: read-only; no writes; low-priv bind.
  • Hints: base DN is your domain root; scope sub.
  • Concepts: base DN, scope, filter.

Beginner 2 — Read the domain password policy.

  • Objective: find the account-lockout threshold and password minimum length before any spraying is even discussed.
  • Requirements: query the domain root object attributes lockoutThreshold, minPwdLength, maxPwdAge.
  • Output: the three values.
  • Constraints: read-only.
  • Hints: -s base on the domain DN reads the domain object itself.
  • Concepts: attribute selection, scope base.

Intermediate 1 — Build the roastable target lists.

  • Objective: produce the Kerberoastable (SPN) and AS-REP-roastable (pre-auth-disabled) account lists.
  • Requirements: two searches — (&(objectClass=user)(servicePrincipalName=*)) and the ...803:=4194304 bitwise filter.
  • Output: two deduplicated sAMAccountName lists; note any account on both.
  • Constraints: enumeration only — do NOT request tickets or roast.
  • Hints: the OID is the bitwise-AND matching rule; 4194304 = 0x400000.
  • Concepts: bitwise userAccountControl filters.

Intermediate 2 — Hunt secrets in attributes, then remediate.

  • Objective: find any user with a value in description/info, then close the leak.
  • Requirements: search (description=*); for any hit containing a credential, clear the attribute (Set-ADUser -Clear description) and note that the secret must be rotated in a vault.
  • Input/Output: input = matched accounts; output = a before/after showing the attribute now empty.
  • Constraints: never paste the found secret into logs or notes; record only "secret present."
  • Hints: omit the attribute list to catch secrets hiding in unexpected fields.
  • Concepts: attribute hygiene, remediation. Defensive conclusion: remediate (clear + rotate) and verify (re-run the search -> zero hits).

Challenge — Prove detection works end-to-end.

  • Objective: make your own enumeration visible in the logs and verify the remediations.
  • Requirements: (1) enable expensive-search logging (Field Engineering level 5) on the lab DC; (2) run the roastable + description sweeps; (3) confirm event 1644 recorded them and correlate with logon 4624; (4) apply the fixes from Intermediate 2 and the SPN/pre-auth remediations; (5) re-run every offensive search and show each now returns zero while a normal lookup still succeeds.
  • Constraints: lab only; reset the diagnostic registry value and restore any test objects afterward.
  • Hints: baseline your own known query sources first so the alert is meaningful.
  • Concepts: detection/logging, mitigation verification, cleanup/reset. Defensive conclusion: the engagement is only "done" when the bad state is both fixed and observable, and legitimate use still works.

Lab cleanup / reset: restore any attributes or flags you changed to their intended secure state (not the vulnerable one), revert the 15 Field Engineering registry value if your lab baseline expects it off, re-enable/rotate any test credential you touched, and snapshot-revert the VM if the lab supports it.

Summary

Main concepts. Active Directory is an LDAP directory queried on 389/636; the defining fact is that any authenticated domain user can read most of it. That single low-privilege read yields the full attack surface: Kerberoastable (SPN) accounts, AS-REP-roastable (pre-auth-disabled) accounts, effective admins revealed by nested groups, stale privileged accounts, and secrets leaked into readable attributes like description.

Key syntax/commands. An LDAP search = base DN + scope + filter + attributes. Filters are parenthesised prefix notation: (&(objectCategory=person)(objectClass=user)), (servicePrincipalName=*), and the bitwise rule (userAccountControl:1.2.840.113556.1.4.803:=4194304) for pre-auth-disabled. ldapsearch -x -H ldap://dc -D user -w pass -b base -s sub 'filter' attrs.

Common mistakes. Thinking you need admin rights (you don't); enumerating outside your written authorization; confusing reading a value with verifying it (decoding a JWT is not verifying its signature); and trusting a clean scanner as proof of security (it never is — nothing is completely secure).

What to remember. Defense cannot block reads, so it is trim + watch: keep secrets out of readable attributes, minimise SPNs, require pre-auth, clean up stale admins — and log LDAP queries (event 1644), baseline them, and seed honey objects so bulk enumeration is detected. Never log the secrets you find. Every finding gets a remediation and a verification that the fix rejects the bad state and accepts the good one. All of this happens only on systems you own or are explicitly authorized to test.