Service bank
DIRECTORY / AD 389/tcp 636/tcp 3268/tcp 3269/tcp

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

PortProtoNotes
389tcpLDAP
636tcpLDAPS (TLS)
3268tcpGlobal Catalog LDAP
3269tcpGlobal 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

PathHoldsSensitive
/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

References