LDAP
aka Active Directory LDAP, LDAPS
Directory protocol on 389 (636 TLS). An anonymous or null bind often hands over all domain user/group data; authenticated binds let you enumerate every AD object. The foundation of all AD pre-attack recon.
Ports
| Port | Proto | Notes |
|---|---|---|
389 | tcp | LDAP |
636 | tcp | LDAPS (TLS) |
3268 | tcp | Global Catalog LDAP |
3269 | tcp | Global Catalog LDAPS |
Fingerprint
- nmap -p389 --script ldap-rootdse leaks domain, forest, and DC hostname pre-auth
- Anonymous bind check: ldapsearch -H ldap://TARGET -x -b '' -s base
Key files
| Path | Holds | Sensitive |
|---|---|---|
/etc/ldap/ldap.conf | client config (URI, base DN) |
Default / weak creds
anonymous / null bind (read-only, often permitted by default)valid domain user — any account unlocks full directory read
Exploitation primitives
- Anonymous/null bind: dump users, groups, password policy without credentials
- Authenticated bind: enumerate every AD object (users, computers, GPOs, ACLs)
- Password spraying target list: pull all user samAccountNames via LDAP
- Identify Kerberoastable (SPN) and AS-REP-roastable accounts via LDAP filters
Overview
LDAP is the query layer over Active Directory. Even before you have credentials, an anonymous or null bind often exposes the full user and group tree. With any valid domain credential, you can enumerate every object in the directory — users, computers, GPOs, ACLs, and trust relationships.
Enumeration
Leak root info pre-auth (no bind required):
nmap -p389 --script ldap-rootdse <TARGET>
Test for anonymous / null bind:
ldapsearch -H ldap://<TARGET> -x -b "" -s base
Pull the base DN from rootDSE:
ldapsearch -H ldap://<TARGET> -x -b "" -s base namingContexts
Dump all objects with a null bind (works when anonymous access is permitted):
ldapsearch -H ldap://<TARGET> -x -b "DC=domain,DC=local"
Authenticated enumeration
Full dump with valid domain creds:
ldapsearch -H ldap://<TARGET> -x -D "user@domain.local" -w 'password' -b "DC=domain,DC=local"
Dump all users (samAccountName + description — descriptions often hold passwords):
ldapsearch -H ldap://<TARGET> -x -D "user@domain.local" -w 'password' \
-b "DC=domain,DC=local" "(objectClass=user)" samAccountName description
Find Kerberoastable accounts (have an SPN set):
ldapsearch -H ldap://<TARGET> -x -D "user@domain.local" -w 'password' \
-b "DC=domain,DC=local" "(&(objectClass=user)(servicePrincipalName=*))" samAccountName servicePrincipalName
Find AS-REP-roastable accounts (pre-auth disabled):
ldapsearch -H ldap://<TARGET> -x -D "user@domain.local" -w 'password' \
-b "DC=domain,DC=local" "(&(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=4194304))" samAccountName
Dump password policy (lockout threshold before spraying):
ldapsearch -H ldap://<TARGET> -x -b "DC=domain,DC=local" -s sub "*" | grep -i lockoutThreshold
windapsearch (faster wrapper)
Unauthenticated — pull all users:
python3 windapsearch.py --dc-ip <TARGET> -u "" -U
Authenticated — Domain Admins:
python3 windapsearch.py --dc-ip <TARGET> -u user@domain.local -p 'password' --da
Authenticated — all privileged users (recursive):
python3 windapsearch.py --dc-ip <TARGET> -u user@domain.local -p 'password' -PU
ldapdomaindump
Dump and format all AD objects to HTML/JSON/greppable files:
ldapdomaindump -u 'domain\user' -p 'password' ldap://<TARGET>
Output files: domain_users.html, domain_computers.html, domain_groups.html — open in a browser.
LDAPS (TLS, port 636)
ldapsearch -H ldaps://<TARGET>:636 -x -D "user@domain.local" -w 'password' \
-b "DC=domain,DC=local" "(objectClass=user)" samAccountName
Add -o TLS_REQCERT=never to ignore cert errors on self-signed DCs.
Hardening
Disable anonymous binds, enforce LDAP signing, restrict directory read to service accounts, and audit LDAP query logs for bulk enumeration.
Seen on these machines 3