LDAP Search
Use the domain naming context returned by RootDSE and a known directory identity. These examples use a fictional lab domain; replace it with the directory you are testing.
Credentials:
- Username:
labuser@example.test - Password: entered at the prompt
- Domain:
example.test - Base DN:
DC=example,DC=test
Basic ldapsearch Syntax (Modern)
Initial Reconnaissance
Get Naming Contexts (Anonymous)
ldapsearch -x -H ldaps://dc01.example.test -b "" -s base namingContexts
Test Authentication
ldapsearch -x -H ldaps://dc01.example.test \
-D 'labuser@example.test' \
-W \
-b "DC=example,DC=test" \
-s base
Full Domain Dump
ldapsearch -x -H ldaps://dc01.example.test \
-D 'labuser@example.test' \
-W \
-b "DC=example,DC=test" | less
Clean Output (Recommended)
ldapsearch -LLL -x -H ldaps://dc01.example.test \
-D 'labuser@example.test' \
-W \
-b "DC=example,DC=test"
User Enumeration
All Users
ldapsearch -x -H ldaps://dc01.example.test \
-D 'labuser@example.test' \
-W \
-b "DC=example,DC=test" \
"(objectClass=person)" cn mail
All AD User Objects
ldapsearch -x -H ldaps://dc01.example.test \
-D 'labuser@example.test' \
-W \
-b "DC=example,DC=test" \
"(&(objectClass=user)(objectCategory=person))" \
sAMAccountName mail displayName
Users with Extended Attributes
ldapsearch -x -H ldaps://dc01.example.test \
-D 'labuser@example.test' \
-W \
-b "DC=example,DC=test" \
"(objectClass=user)" \
sAMAccountName mail userAccountControl description info memberOf
Search for Passwords in Description/Info Fields
# Check description fields
ldapsearch -x -H ldaps://dc01.example.test \
-D 'labuser@example.test' \
-W \
-b "DC=example,DC=test" \
"(description=*)" description cn | grep -i "pass\|pwd"
# Check info field (verify readable values)
ldapsearch -x -H ldaps://dc01.example.test \
-D 'labuser@example.test' \
-W \
-b "DC=example,DC=test" \
"(info=*)" info cn sAMAccountName
Find One User Specifically
ldapsearch -x -H ldaps://dc01.example.test \
-D 'labuser@example.test' \
-W \
-b "DC=example,DC=test" \
"(sAMAccountName=service-user)" \
cn info memberOf distinguishedName
Active Users Only (Exclude Disabled)
ldapsearch -x -H ldaps://dc01.example.test \
-D 'labuser@example.test' \
-W \
-b "DC=example,DC=test" \
'(&(objectClass=user)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))' \
sAMAccountName cn
Service Accounts (Kerberoastable)
ldapsearch -x -H ldaps://dc01.example.test \
-D 'labuser@example.test' \
-W \
-b "DC=example,DC=test" \
"(&(objectCategory=person)(objectClass=user)(servicePrincipalName=*))" \
sAMAccountName servicePrincipalName
Group Enumeration
All Groups
ldapsearch -x -H ldaps://dc01.example.test \
-D 'labuser@example.test' \
-W \
-b "DC=example,DC=test" \
"(objectClass=group)" cn description member
Groups with “Admin” in Name
ldapsearch -LLL -x -H ldaps://dc01.example.test \
-D 'labuser@example.test' \
-W \
-b "DC=example,DC=test" \
"(&(objectClass=group)(name=*admin*))" name sAMAccountName member
Shared Support Accounts Group
ldapsearch -x -H ldaps://dc01.example.test \
-D 'labuser@example.test' \
-W \
-b "DC=example,DC=test" \
"(cn=Shared Support Accounts)" member
Remote Management Users Group
ldapsearch -x -H ldaps://dc01.example.test \
-D 'labuser@example.test' \
-W \
-b "DC=example,DC=test" \
"(cn=Remote Management Users)" member
Domain Admins
ldapsearch -x -H ldaps://dc01.example.test \
-D 'labuser@example.test' \
-W \
-b "DC=example,DC=test" \
"(cn=Domain Admins)" member
User’s Group Memberships
ldapsearch -x -H ldaps://dc01.example.test \
-D 'labuser@example.test' \
-W \
-b "DC=example,DC=test" \
"(sAMAccountName=service-user)" memberOf
Computer Enumeration
All Computers
ldapsearch -x -H ldaps://dc01.example.test \
-D 'labuser@example.test' \
-W \
-b "DC=example,DC=test" \
"(objectClass=computer)" cn operatingSystem dNSHostName
Domain Controllers Only
ldapsearch -x -H ldaps://dc01.example.test \
-D 'labuser@example.test' \
-W \
-b "DC=example,DC=test" \
"(userAccountControl:1.2.840.113556.1.4.803:=8192)" cn dNSHostName
Computers with Unconstrained Delegation
ldapsearch -x -H ldaps://dc01.example.test \
-D 'labuser@example.test' \
-W \
-b "DC=example,DC=test" \
"(userAccountControl:1.2.840.113556.1.4.803:=524288)" cn
Organizational Units
ldapsearch -x -LLL -H ldaps://dc01.example.test \
-D 'labuser@example.test' \
-W \
-b "DC=example,DC=test" \
-s sub \
"(|(objectClass=organizationalUnit)(objectClass=group))"
Operational Attributes
# Get all operational attributes
ldapsearch -x -H ldaps://dc01.example.test \
-D 'labuser@example.test' \
-W \
-b "DC=example,DC=test" \
"(objectClass=*)" '+'
# Specific operational attributes
ldapsearch -x -H ldaps://dc01.example.test \
-D 'labuser@example.test' \
-W \
-b "DC=example,DC=test" \
"(objectClass=*)" \
creatorsName createTimestamp modifiersName modifyTimestamp
Modern Tools
ldapdomaindump
# Comprehensive domain dump
ldapdomaindump -u 'example.test\labuser' \
-p '<LAB_PASSWORD>' \
example.test \
-o ldap_output
# Output creates:
# - domain_users.json/html
# - domain_groups.json/html
# - domain_computers.json/html
# - domain_trusts.json/html
# - domain_policy.json/html
BloodHound Python
bloodhound-python -c All \
-u labuser \
-p '<LAB_PASSWORD>' \
-d example.test \
-ns 192.0.2.10
CrackMapExec / NetExec
# Verify credentials
nxc smb dc01.example.test \
-u labuser \
-p '<LAB_PASSWORD>'
# LDAP enumeration
nxc ldap dc01.example.test \
-u labuser \
-p '<LAB_PASSWORD>' \
--users --groups --computers
Secure Alternative (Password Prompt)
Instead of putting the password in the command, use -W for a prompt:
ldapsearch -x -H ldaps://dc01.example.test \
-D 'labuser@example.test' \
-W \
-b "DC=example,DC=test"
LDAPS (Secure LDAP)
ldapsearch -x -H ldaps://dc01.example.test:636 \
-D 'labuser@example.test' \
-W \
-b "DC=example,DC=test"
Key Takeaways
- Use
-Hwith a complete URI; choose LDAPS or required StartTLS for password binds - Add
-xfor simple authentication in modern versions - Use
-LLLfor cleaner output - The
infofield can disclose sensitive free-text configuration; inspect only fields needed for the test - Check group memberships - group membership is a lead; endpoint ACLs and policy decide WinRM access
Command-Line Flags Reference Table
| Flag | Description | Example |
|---|---|---|
-H | LDAP URI to connect to (replaces deprecated -h) | -H ldaps://dc01.example.test |
-D | Bind Distinguished Name for authentication | -D 'labuser@example.test' |
-w | Bind password (plaintext - visible in process list) | -w 'password' |
-W | Prompt for bind password (more secure) | -W |
-y | Read password from file | -y /path/to/passfile |
-b | Base Distinguished Name for search | -b "DC=example,DC=test" |
-s | Search scope: base, one, sub, children | -s sub |
-x | Use simple authentication instead of SASL | -x |
-Z / -ZZ | Request / require successful StartTLS | -ZZ |
-L | LDIFv1 format (one -L) | -L |
-LL | Disable comments in output (two -L) | -LL |
-LLL | Disable comments and version (three -L, cleanest) | -LLL |
-v | Verbose output | -v |
-d | LDAP debug bitmask; see the installed manual | -d 1 |
-A | Retrieve attribute names only (no values) | -A |
-l | Time limit for search in seconds | -l 30 |
-z | Size limit for number of entries returned | -z 100 |
-S | Sort results by specified attribute | -S cn |
-E | LDAP extensions (e.g., paging) | -E pr=1000/noprompt |
-o | Set general options | -o ldif_wrap=no |
-n | Show what would be done (dry run) | -n |
-u | Include User Friendly names in output | -u |
-t | Write binary values to temp files | -t |
-T | Directory for temp files (use with -t) | -T /tmp |
-F | URL prefix for temp files | -F file:///tmp/ |
-M | Enable Manage DSA IT control | -M |
-c | Continuous operation mode (ignore errors) | -c |
Search Scope Values
| Scope | Description |
|---|---|
base | Search only the base DN itself |
one | Search immediate children of base DN only (one level) |
sub | Search base DN and all descendants (subtree - most common) |
children | Search all descendants but not the base DN itself |
Common Attribute Shortcuts
| Shortcut | Meaning |
|---|---|
* | All regular (non-operational) attributes |
+ | All operational attributes |
1.1 | No attributes (DN only) |
* + | All attributes (regular + operational) |
LDAP URI Format
| Format | Description |
|---|---|
ldap://host | Standard LDAP on port 389 |
ldap://host:port | LDAP on custom port |
ldaps://host | LDAP over SSL/TLS on port 636 |
ldaps://host:port | LDAPS on custom port |
ldapi:// | LDAP over Unix domain socket (local) |
Common Exit Codes
| Code | Meaning |
|---|---|
0 | Success |
1 | Operations error |
2 | Protocol error |
32 | No such object |
49 | Invalid credentials |
50 | Insufficient access rights |
Pro Tips
- Always use
-LLLfor clean, parseable output - Use
-Winstead of-wto avoid password in shell history - The
infofield in AD often contains sensitive data - Check group memberships - Remote Management Users can permit WinRM, subject to endpoint ACLs and policy
- Operational attributes (
+) reveal creation/modification metadata - Use
subscope for comprehensive searches - Combine filters with
&(AND) and|(OR) for precise queries - Save output to files for offline analysis with
> output.txt
A repeatable domain-discovery workflow
Discover the base DN from RootDSE
ldapsearch -LLL -x -H ldaps://dc01.example.test \
-D 'labuser@example.test' -W -b '' -s base '(objectClass=*)' \
defaultNamingContext namingContexts dnsHostName supportedSASLMechanisms
Use defaultNamingContext for the domain partition. Configuration, schema and DNS application partitions have different purposes. RootDSE can be readable anonymously even when normal directory searches require a bind; anonymous RootDSE access is not proof of anonymous domain enumeration.
Page through the actual domain partition
ldapsearch -LLL -x -H ldaps://dc01.example.test \
-D 'labuser@example.test' -W -b 'DC=example,DC=test' \
-E pr=500/noprompt -o ldif_wrap=no \
'(&(objectCategory=person)(objectClass=user))' \
sAMAccountName distinguishedName userAccountControl memberOf \
> domain-users.ldif
Paging avoids confusing a server size limit with a complete inventory. -z sets the requested total size limit; it does not replace a paging control. Keep stderr and the final LDAP result when deciding whether a search completed.
Require StartTLS when using port 389
ldapsearch -LLL -x -ZZ -H ldap://dc01.example.test \
-D 'labuser@example.test' -W -b 'DC=example,DC=test' \
-s base '(objectClass=*)' distinguishedName
-ZZ requires successful StartTLS. -Z requests it but does not impose the same requirement. Use the issuing CA in the client trust configuration and the certificate’s hostname; disabling verification conceals the transport problem.
Nested groups and primary groups
# AD transitive membership match: user is nested in this group
ldapsearch -LLL -x -H ldaps://dc01.example.test \
-D 'labuser@example.test' -W -b 'DC=example,DC=test' \
'(&(objectClass=user)(memberOf:1.2.840.113556.1.4.1941:=CN=Lab Operators,OU=Groups,DC=example,DC=test))' \
sAMAccountName memberOf primaryGroupID objectSid
This matching rule is AD-specific. Ordinary memberOf lists direct memberships and omits the primary group; neither is a complete effective-token calculation. Large group’s member values can be returned as ranged attributes such as member;range=0-1499. Fetch subsequent ranges before calling that membership list complete.
LDIF parsing and filter escaping
| Syntax | Meaning |
|---|---|
attribute: value | Ordinary textual value |
attribute:: value | Base64-encoded value, not necessarily a secret |
| Line beginning with a space | Continuation of the previous LDIF line |
\2a, \28, \29, \5c, \00 | Escaped *, (, ), backslash and NUL in an LDAP filter |
Filter escaping and DN escaping are different operations. Do not interpolate unescaped names into a filter. -o ldif_wrap=no helps text inspection but does not turn LDIF into CSV; use a parser for reliable exports. Binary attributes such as objectSid need decoding rather than treating their displayed base64 as the SID string.
Failure diagnosis and next step
| LDAP result | Investigation |
|---|---|
32 / no such object | Base DN and which partition exists on this DC |
49 / invalid credentials | Bind identity, password and account restrictions |
50 / insufficient access | Object/attribute permissions for this principal |
4 / size limit exceeded | Paging and server policy; output is partial |
| Stronger authentication required | Signing/SASL/TLS requirements; do not disable policy as a fix |
| Referral | Different partition/domain; follow deliberately with the intended identity |
Record DC, base, filter, attributes, bind identity, timestamp and whether the search completed. Use SharpHound for graph relationships, gMSADumper for managed-password access, and BloodyAD for a verified object-control operation.
LDAP reference
The OpenLDAP ldapsearch manual documents short options, paging, SASL and LDIF output. It does not define the GNU-style long option aliases sometimes shown in copied cheat sheets.