Skip to content
HackIndex logo

HackIndex

LDAP Anonymous Enumeration

4 min read May 15, 2026

When anonymous bind succeeds, you can query the directory without credentials. The scope of what you can read varies — some servers expose the full tree, others scope anonymous access to specific OUs or attributes. Start broad and narrow down based on what the server returns.

Before running any query here, confirm anonymous bind works and get your base DN from LDAP RootDSE and Anonymous Bind Checks.

Full directory dump

Start with a broad dump to see what the server exposes. Save it to a file — the output is often large and you want to grep through it offline.

┌──(kali㉿kali)-[~]
└─$ # Dump everything readable
┌──(kali㉿kali)-[~]
└─$ ldapsearch -x -H ldap://$TARGET_IP -b "$BASE_DN" > ldap-dump.txt
 
┌──(kali㉿kali)-[~]
└─$ # Dump with all attributes explicitly
┌──(kali㉿kali)-[~]
└─$ ldapsearch -x -H ldap://$TARGET_IP -b "$BASE_DN" "(objectClass=*)" "*" > ldap-dump.txt
 
┌──(kali㉿kali)-[~]
└─$ # Quick preview of what object types exist
┌──(kali㉿kali)-[~]
└─$ ldapsearch -x -H ldap://$TARGET_IP -b "$BASE_DN" "(objectClass=*)" objectClass | grep "^objectClass" | sort | uniq -c | sort -rn

Users

Pull all user objects with the attributes most useful for follow-on attacks. The description attribute is critical — administrators frequently store temporary passwords there.

┌──(kali㉿kali)-[~]
└─$ # All users — key attributes
┌──(kali㉿kali)-[~]
└─$ ldapsearch -x -H ldap://$TARGET_IP -b "$BASE_DN" "(objectClass=user)" sAMAccountName userPrincipalName description memberOf
 
┌──(kali㉿kali)-[~]
└─$ # Just usernames — clean list for spraying
┌──(kali㉿kali)-[~]
└─$ ldapsearch -x -H ldap://$TARGET_IP -b "$BASE_DN" "(objectClass=user)" sAMAccountName | grep "^sAMAccountName:" | awk '{print $2}'
 
┌──(kali㉿kali)-[~]
└─$ # OpenLDAP / non-AD
┌──(kali㉿kali)-[~]
└─$ ldapsearch -x -H ldap://$TARGET_IP -b "$BASE_DN" "(objectClass=posixAccount)" uid cn homeDirectory loginShell
 
┌──(kali㉿kali)-[~]
└─$ # Check descriptions for passwords
┌──(kali㉿kali)-[~]
└─$ ldapsearch -x -H ldap://$TARGET_IP -b "$BASE_DN" "(objectClass=user)" sAMAccountName description | grep -B1 "description:"
# user1, Users, corp.local
dn: CN=user1,CN=Users,DC=corp,DC=local
sAMAccountName: user1
description: Password123! - temp password set by IT

Any password found in the description field is live domain credential data. Test it immediately against SMB, WinRM, and RDP before continuing enumeration. The username list from sAMAccountName values feeds directly into AS-REP Roasting — you can request AS-REP hashes for any of these accounts without knowing their password if pre-auth is disabled.

Groups

┌──(kali㉿kali)-[~]
└─$ # All groups
┌──(kali㉿kali)-[~]
└─$ ldapsearch -x -H ldap://$TARGET_IP -b "$BASE_DN" "(objectClass=group)" cn member
 
┌──(kali㉿kali)-[~]
└─$ # High-value groups only
┌──(kali㉿kali)-[~]
└─$ ldapsearch -x -H ldap://$TARGET_IP -b "$BASE_DN" "(cn=Domain Admins)" member
┌──(kali㉿kali)-[~]
└─$ ldapsearch -x -H ldap://$TARGET_IP -b "$BASE_DN" "(cn=Enterprise Admins)" member
┌──(kali㉿kali)-[~]
└─$ ldapsearch -x -H ldap://$TARGET_IP -b "$BASE_DN" "(cn=Backup Operators)" member
┌──(kali㉿kali)-[~]
└─$ ldapsearch -x -H ldap://$TARGET_IP -b "$BASE_DN" "(cn=Account Operators)" member
┌──(kali㉿kali)-[~]
└─$ ldapsearch -x -H ldap://$TARGET_IP -b "$BASE_DN" "(cn=Remote Desktop Users)" member
┌──(kali㉿kali)-[~]
└─$ ldapsearch -x -H ldap://$TARGET_IP -b "$BASE_DN" "(cn=DNSAdmins)" member
 
┌──(kali㉿kali)-[~]
└─$ # All groups with members listed
┌──(kali㉿kali)-[~]
└─$ ldapsearch -x -H ldap://$TARGET_IP -b "$BASE_DN" "(objectClass=group)" cn member | grep -E "^cn:|^member:"

Computers

┌──(kali㉿kali)-[~]
└─$ # All computers
┌──(kali㉿kali)-[~]
└─$ ldapsearch -x -H ldap://$TARGET_IP -b "$BASE_DN" "(objectClass=computer)" dNSHostName operatingSystem sAMAccountName
 
┌──(kali㉿kali)-[~]
└─$ # Domain controllers only
┌──(kali㉿kali)-[~]
└─$ ldapsearch -x -H ldap://$TARGET_IP -b "$BASE_DN" "(&(objectClass=computer)(userAccountControl:1.2.840.113556.1.4.803:=8192))" dNSHostName

Password policy

Always pull the password policy before attempting any brute force or spraying. The lockoutThreshold value determines how many attempts you can make per account before locking it out. A value of 0 means no lockout.

┌──(kali㉿kali)-[~]
└─$ # Domain password policy
┌──(kali㉿kali)-[~]
└─$ ldapsearch -x -H ldap://$TARGET_IP -b "$BASE_DN" "(objectClass=domainDNS)" minPwdLength lockoutThreshold lockoutDuration pwdHistoryLength maxPwdAge
 
┌──(kali㉿kali)-[~]
└─$ # Alternative filter
┌──(kali㉿kali)-[~]
└─$ ldapsearch -x -H ldap://$TARGET_IP -b "$BASE_DN" "(objectClass=domain)" | grep -E "lockout|pwd|Pwd"
minPwdLength: 7
lockoutThreshold: 0
maxPwdAge: -37108517437440000

lockoutThreshold: 0 means no lockout policy — you can brute force accounts freely. A non-zero threshold means you need to respect it or risk locking out accounts. Feed this into your spraying strategy — check AD Password Policy and Spray Surface before touching LDAP Brute Force and Password Spraying.

nxc anonymous checks

┌──(kali㉿kali)-[~]
└─$ # Users
┌──(kali㉿kali)-[~]
└─$ nxc ldap $TARGET_IP -u '' -p '' --users
 
┌──(kali㉿kali)-[~]
└─$ # Password not required (may authenticate with blank password)
┌──(kali㉿kali)-[~]
└─$ nxc ldap $TARGET_IP -u '' -p '' --password-not-required
 
┌──(kali㉿kali)-[~]
└─$ # Groups
┌──(kali㉿kali)-[~]
└─$ nxc ldap $TARGET_IP -u '' -p '' --groups
LDAP  10.10.10.10  389  DC01  [+] corp.local\ 
LDAP  10.10.10.10  389  DC01  [*] Enumerated 24 domain users
LDAP  10.10.10.10  389  DC01  corp.local\Administrator  badpwdcount: 0  desc: Built-in account for administering
LDAP  10.10.10.10  389  DC01  corp.local\svc-backup  badpwdcount: 0  desc: Password123! - temp

What to do with the output