ldapsearch is a command-line utility from the OpenLDAP project used to query LDAP (Lightweight Directory Access Protocol) directory servers, most commonly Microsoft Active Directory Domain Controllers, but also standalone OpenLDAP, FreeIPA, and other directory services. LDAP is the protocol that underlies Active Directory’s object storage (users, groups, computers, organizational units, group policies), and because many AD environments permit unauthenticated (“anonymous”) binds or low-privilege authenticated binds by default, ldapsearch can extract an enormous amount of domain intelligence directly — often without needing any specialized AD tooling.
During enumeration, ldapsearch is used to:
- Discover the LDAP base DN (distinguished name) of the directory
- Enumerate all user accounts, their attributes (
sAMAccountName,userPrincipalName,description,memberOf, etc.) - Enumerate groups and their membership
- Enumerate computer objects (workstations/servers joined to the domain)
- Pull password policy and domain functional level information
- Search for specific, sensitive attributes (e.g.,
descriptionfields sometimes contain plaintext passwords left by administrators)
It is a foundational tool for Active Directory enumeration and is frequently the very first thing run against port 389/636 once a Domain Controller is identified.
Installation
# Kali Linux (preinstalled as part of ldap-utils)
sudo apt update
sudo apt install ldap-utils -y
# Verify
ldapsearch -VV
which ldapsearch
Syntax
ldapsearch -x -H ldap://<target> -b "<base DN>" [options] [filter] [attributes]
Command-Line Options
| Option | Description |
|---|---|
-x | Use simple authentication instead of SASL |
-H uri | LDAP URI, e.g., ldap://192.168.10.10 or ldaps://192.168.10.10 |
-b basedn | Base DN to search from, e.g., "DC=corp,DC=local" |
-D binddn | Distinguished name to bind with (for authenticated bind) |
-w password | Password for the bind DN (plaintext, on command line) |
-W | Prompt interactively for bind password (safer than -w) |
-s scope | Search scope: base, one, or sub (default sub) |
-LLL | Minimal output: no comments, no version, no line wrapping |
-o ldif-wrap=no | Disable LDIF line wrapping for cleaner parsing |
-Z / -ZZ | Use StartTLS (optional/required) |
-p port | Specify port (default 389, or 636 for LDAPS) |
-v | Verbose |
-d level | Debug level |
<filter> | LDAP search filter, e.g., "(objectClass=user)" |
<attrs> | Space-separated list of attributes to return, e.g., sAMAccountName mail |
Common useful filters:
| Filter | Purpose |
|---|---|
(objectClass=*) | Return everything (used with base scope to get root DSE) |
(objectClass=user) | All user objects |
(objectClass=group) | All group objects |
(objectClass=computer) | All computer objects |
(&(objectClass=user)(admincount=1)) | Privileged accounts (protected by AdminSDHolder) |
(userAccountControl:1.2.840.113556.1.4.803:=2) | Disabled accounts |
(description=*) | Objects with a non-empty description field |
Basic Usage
Query the root DSE (anonymous bind) to discover the base DN and server capabilities:
ldapsearch -x -H ldap://192.168.10.10 -s base -b "" "(objectClass=*)" namingContexts
Expected output:
# extended LDIF
#
# LDAPv3
# base <> with scope baseObject
# filter: (objectClass=*)
# requesting: namingContexts
#
dn:
namingContexts: DC=corp,DC=local
namingContexts: CN=Configuration,DC=corp,DC=local
namingContexts: CN=Schema,CN=Configuration,DC=corp,DC=local
# search result
search: 2
result: 0 Success
Practical Examples
Example 1 — Discover base DN via anonymous root DSE query
ldapsearch -x -H ldap://192.168.10.10 -s base -b "" namingContexts
namingContexts: DC=corp,DC=local
Example 2 — Anonymous bind, dump all objects under the base DN
ldapsearch -x -H ldap://192.168.10.10 -b "DC=corp,DC=local"
# extended LDIF
#
# corp.local
dn: DC=corp,DC=local
objectClass: top
objectClass: domain
...
# Users, Users, corp.local
dn: CN=Users,DC=corp,DC=local
objectClass: container
Example 3 — Enumerate all usernames (sAMAccountName)
ldapsearch -x -H ldap://192.168.10.10 -b "DC=corp,DC=local" \
"(objectClass=user)" sAMAccountName -LLL
dn: CN=Administrator,CN=Users,DC=corp,DC=local
sAMAccountName: Administrator
dn: CN=jdoe,CN=Users,DC=corp,DC=local
sAMAccountName: jdoe
dn: CN=asmith,CN=Users,DC=corp,DC=local
sAMAccountName: asmith
Example 4 — Authenticated bind with domain credentials
ldapsearch -x -H ldap://192.168.10.10 -D "jdoe@corp.local" -W \
-b "DC=corp,DC=local" "(objectClass=user)" sAMAccountName mail
Enter LDAP Password:
dn: CN=jdoe,CN=Users,DC=corp,DC=local
sAMAccountName: jdoe
mail: jdoe@corp.local
Example 5 — Search for privileged (AdminCount=1) accounts
ldapsearch -x -H ldap://192.168.10.10 -D "jdoe@corp.local" -w 'Passw0rd!' \
-b "DC=corp,DC=local" "(&(objectClass=user)(admincount=1))" sAMAccountName -LLL
dn: CN=Administrator,CN=Users,DC=corp,DC=local
sAMAccountName: Administrator
dn: CN=svc_backup,CN=Users,DC=corp,DC=local
sAMAccountName: svc_backup
Example 6 — Search description fields for accidentally leaked credentials
ldapsearch -x -H ldap://192.168.10.10 -D "jdoe@corp.local" -w 'Passw0rd!' \
-b "DC=corp,DC=local" "(description=*)" sAMAccountName description -LLL
dn: CN=tempuser,CN=Users,DC=corp,DC=local
sAMAccountName: tempuser
description: temp pw Summer2026!
Example 7 — Enumerate all computer objects (workstations/servers)
ldapsearch -x -H ldap://192.168.10.10 -D "jdoe@corp.local" -w 'Passw0rd!' \
-b "DC=corp,DC=local" "(objectClass=computer)" dNSHostName operatingSystem -LLL
dn: CN=DC01,OU=Domain Controllers,DC=corp,DC=local
dNSHostName: dc01.corp.local
operatingSystem: Windows Server 2019 Standard
dn: CN=WKSTN05,CN=Computers,DC=corp,DC=local
dNSHostName: wkstn05.corp.local
operatingSystem: Windows 11 Enterprise
Example 8 — Enumerate all groups and their members
ldapsearch -x -H ldap://192.168.10.10 -D "jdoe@corp.local" -w 'Passw0rd!' \
-b "DC=corp,DC=local" "(objectClass=group)" cn member -LLL
dn: CN=Domain Admins,CN=Users,DC=corp,DC=local
cn: Domain Admins
member: CN=Administrator,CN=Users,DC=corp,DC=local
member: CN=asmith,CN=Users,DC=corp,DC=local
Example 9 — Find disabled accounts using a bitwise LDAP filter
ldapsearch -x -H ldap://192.168.10.10 -D "jdoe@corp.local" -w 'Passw0rd!' \
-b "DC=corp,DC=local" "(userAccountControl:1.2.840.113556.1.4.803:=2)" sAMAccountName -LLL
dn: CN=old_svc,CN=Users,DC=corp,DC=local
sAMAccountName: old_svc
Example 10 — Using LDAPS (encrypted, port 636)
ldapsearch -x -H ldaps://192.168.10.10:636 -D "jdoe@corp.local" -w 'Passw0rd!' \
-b "DC=corp,DC=local" "(objectClass=user)" sAMAccountName -LLL
dn: CN=jdoe,CN=Users,DC=corp,DC=local
sAMAccountName: jdoe
Common Use Cases
- Building a full domain username list for password spraying, from either anonymous or low-privilege authenticated LDAP binds
- Mapping Active Directory group structure and privileged group membership (Domain Admins, Enterprise Admins) for attack-path planning
- Hunting for credentials accidentally stored in
description/info/commentattributes on user objects - Enumerating all domain-joined computers, including operating system versions, to identify unpatched or legacy systems
- Confirming whether anonymous LDAP bind is enabled — itself a significant misconfiguration finding to report
Automation with Bash
#!/bin/bash
# ldap_userlist.sh - Extract a clean sAMAccountName list from a Domain Controller
DC="192.168.10.10"
BASEDN="DC=corp,DC=local"
BINDDN="jdoe@corp.local"
BINDPW='Passw0rd!'
OUT="domain_users.txt"
ldapsearch -x -H "ldap://$DC" -D "$BINDDN" -w "$BINDPW" \
-b "$BASEDN" "(objectClass=user)" sAMAccountName -LLL \
| grep "^sAMAccountName:" | awk '{print $2}' | sort -u > "$OUT"
echo "[+] Extracted $(wc -l < "$OUT") usernames to $OUT"
Tips and Best Practices
- Always test an anonymous bind first (omit
-D/-w); many AD environments still permit anonymous LDAP reads, especially the root DSE / base DN. - Use
-LLLand-o ldif-wrap=notogether for clean, script-parseable output free of comments and line wraps. - Prefer
-W(interactive password prompt) over-won the command line during manual testing, since-wexposes the password in shell history/process listing. - Use the
admincount=1filter as a fast way to shortlist high-value accounts protected by AdminSDHolder, which typically includes Domain/Enterprise Admins and built-in service accounts. - Always try LDAPS (
-H ldaps://...port 636) if plain LDAP (389) is filtered or refuses simple bind — many environments enforce encrypted-only LDAP.
Troubleshooting
| Problem | Likely Cause | Solution |
|---|---|---|
ldap_bind: Invalid credentials (49) | Wrong bind DN format or password | Try user@domain.tld format, or full CN=...,DC=...,DC=... DN |
ldap_sasl_bind(SIMPLE): Can't contact LDAP server (-1) | Port blocked or wrong protocol/port combo | Confirm with nmap -p389,636 <ip>; try ldaps:// on 636 |
| Anonymous bind returns nothing | Anonymous LDAP disabled (default on modern AD) | Requires valid domain credentials (even a low-privilege account is usually enough) |
ldap_bind: Strong(er) authentication required (8) | Server enforces signed/encrypted LDAP | Use -Z for StartTLS or connect via ldaps:// |
| Search returns partial results only | Default LDAP result size/time limits on the server | Add -z 0 (no size limit, if permitted) or narrow search scope/filter |
References
- OpenLDAP
ldapsearchmanual: https://www.openldap.org/software/man.cgi?query=ldapsearch - Kali Linux tool page: https://www.kali.org/tools/ldap-utils/
- Microsoft LDAP filter syntax reference: https://learn.microsoft.com/en-us/windows/win32/adsi/search-filter-syntax