AD^:Active Directory

LDAP Search

LDAP discovery, authenticated searches, paging, AD filters, nested groups, TLS and result interpretation with reusable lab examples.

intermediateupdated 2026-10-12NetExec · BloodHound · ldapsearch

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

  1. Use -H with a complete URI; choose LDAPS or required StartTLS for password binds
  2. Add -x for simple authentication in modern versions
  3. Use -LLL for cleaner output
  4. The info field can disclose sensitive free-text configuration; inspect only fields needed for the test
  5. Check group memberships - group membership is a lead; endpoint ACLs and policy decide WinRM access

Command-Line Flags Reference Table

FlagDescriptionExample
-HLDAP URI to connect to (replaces deprecated -h)-H ldaps://dc01.example.test
-DBind Distinguished Name for authentication-D 'labuser@example.test'
-wBind password (plaintext - visible in process list)-w 'password'
-WPrompt for bind password (more secure)-W
-yRead password from file-y /path/to/passfile
-bBase Distinguished Name for search-b "DC=example,DC=test"
-sSearch scope: base, one, sub, children-s sub
-xUse simple authentication instead of SASL-x
-Z / -ZZRequest / require successful StartTLS-ZZ
-LLDIFv1 format (one -L)-L
-LLDisable comments in output (two -L)-LL
-LLLDisable comments and version (three -L, cleanest)-LLL
-vVerbose output-v
-dLDAP debug bitmask; see the installed manual-d 1
-ARetrieve attribute names only (no values)-A
-lTime limit for search in seconds-l 30
-zSize limit for number of entries returned-z 100
-SSort results by specified attribute-S cn
-ELDAP extensions (e.g., paging)-E pr=1000/noprompt
-oSet general options-o ldif_wrap=no
-nShow what would be done (dry run)-n
-uInclude User Friendly names in output-u
-tWrite binary values to temp files-t
-TDirectory for temp files (use with -t)-T /tmp
-FURL prefix for temp files-F file:///tmp/
-MEnable Manage DSA IT control-M
-cContinuous operation mode (ignore errors)-c

Search Scope Values

ScopeDescription
baseSearch only the base DN itself
oneSearch immediate children of base DN only (one level)
subSearch base DN and all descendants (subtree - most common)
childrenSearch all descendants but not the base DN itself

Common Attribute Shortcuts

ShortcutMeaning
*All regular (non-operational) attributes
+All operational attributes
1.1No attributes (DN only)
* +All attributes (regular + operational)

LDAP URI Format

FormatDescription
ldap://hostStandard LDAP on port 389
ldap://host:portLDAP on custom port
ldaps://hostLDAP over SSL/TLS on port 636
ldaps://host:portLDAPS on custom port
ldapi://LDAP over Unix domain socket (local)

Common Exit Codes

CodeMeaning
0Success
1Operations error
2Protocol error
32No such object
49Invalid credentials
50Insufficient access rights

Pro Tips

  1. Always use -LLL for clean, parseable output
  2. Use -W instead of -w to avoid password in shell history
  3. The info field in AD often contains sensitive data
  4. Check group memberships - Remote Management Users can permit WinRM, subject to endpoint ACLs and policy
  5. Operational attributes (+) reveal creation/modification metadata
  6. Use sub scope for comprehensive searches
  7. Combine filters with & (AND) and | (OR) for precise queries
  8. 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

SyntaxMeaning
attribute: valueOrdinary textual value
attribute:: valueBase64-encoded value, not necessarily a secret
Line beginning with a spaceContinuation of the previous LDIF line
\2a, \28, \29, \5c, \00Escaped *, (, ), 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 resultInvestigation
32 / no such objectBase DN and which partition exists on this DC
49 / invalid credentialsBind identity, password and account restrictions
50 / insufficient accessObject/attribute permissions for this principal
4 / size limit exceededPaging and server policy; output is partial
Stronger authentication requiredSigning/SASL/TLS requirements; do not disable policy as a fix
ReferralDifferent 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.

On this page

☧ CPTS · ordered posts

Locked

Unlock with your Main_Key through local GPG.

Start the local GPG helper

In your cheatsheet checkout, run:

npm run unlock:helper

Open the link it prints, or paste its pairing code below. Keep the terminal open until GPG finishes.

Import an index you already decrypted

Download the encrypted index, then decrypt with GPG:

gpg --output report-index.json --decrypt report-index.gpg

Your private key stays in GPG. The decrypted index is saved in this browser so you can reopen it after navigation, refreshes and restarts. Lock this browser or clear this site's browser data to remove the saved index. Nothing is uploaded.