LDAP injection is the forgotten sibling of SQL injection. While SQL injection gets the headlines, LDAP injection quietly compromises enterprise authentication systems, Active Directory environments, and corporate intranets. Any application that authenticates users against an LDAP directory or searches directory data based on user input is a potential target.
The attack surface is significant. LDAP directories store user accounts, group memberships, organizational structures, and access control information for most enterprise environments. Compromising LDAP queries means compromising the identity infrastructure that everything else depends on.
How LDAP Queries Work
LDAP queries use a filter syntax based on parentheses and operators:
- Simple filter:
(cn=John Smith) - AND filter:
(&(cn=John Smith)(department=Engineering)) - OR filter:
(|(cn=John)(cn=Jane)) - NOT filter:
(!(cn=John Smith)) - Wildcard:
(cn=J*) - Presence:
(cn=*)
When an application builds LDAP filters by concatenating user input, the same class of injection vulnerabilities that plague SQL applies to LDAP.
Authentication Bypass
The most common LDAP injection target is authentication. A typical vulnerable login function builds a filter like:
(&(uid=[username])(userPassword=[password]))
If the attacker supplies *)(uid=*))(|(uid=* as the username, the filter becomes:
(&(uid=*)(uid=*))(|(uid=*)(userPassword=anything))
This matches any user with any UID -- effectively bypassing authentication. The LDAP server returns the first matching entry, which is usually an administrative account.
Simpler bypass payloads include:
- Username:
*-- matches any UID - Username:
admin)(&)-- closes the filter and adds an always-true condition - Username:
admin)(|(password=*-- modifies the filter logic
Prevention:
- Never concatenate user input into LDAP filters
- Use parameterized LDAP queries (LDAP libraries support this)
- Validate that username and password contain only expected characters
- Escape special LDAP characters:
(,),*,\, NUL
Data Extraction Through LDAP Injection
Beyond authentication bypass, LDAP injection can extract directory information:
Attribute enumeration. By injecting wildcards and observing response differences, attackers can enumerate user attributes, group memberships, and organizational structure.
Blind LDAP injection. When the application does not return LDAP query results directly, boolean-based blind injection can extract data character by character. The attacker tests conditions like (uid=a*) and observes whether the response indicates a match or not.
For example, to extract the admin's password hash:
- Test
(&(uid=admin)(userPassword=a*))-- does it match? - Test
(&(uid=admin)(userPassword=b*))-- does it match? - Continue until the first character is found
- Repeat for subsequent characters
User enumeration. Injecting into search filters reveals which usernames exist:
(uid=alice)returns results -- alice exists(uid=bob)returns no results -- bob does not exist
Prevention:
- Return generic error messages that do not reveal whether the query matched
- Rate limit authentication and search endpoints
- Log and alert on queries with special characters
- Monitor for sequential or patterned queries indicating blind injection
LDAP Injection in Search Functions
Many enterprise applications include LDAP-backed search functionality -- employee directories, address books, group management. These search functions often accept user input that goes directly into LDAP filters:
Directory search poisoning. A search for employees in the "Engineering" department builds the filter (department=[input]). The attacker submits Engineering)(uid=* to extract all UIDs regardless of department.
Group membership manipulation. Applications that manage LDAP group memberships based on user input can be tricked into adding users to groups they should not belong to, such as Domain Admins.
Organizational unit traversal. LDAP queries specify a base DN (distinguished name) for the search. If the base DN is influenced by user input, the attacker can redirect the search to a different part of the directory tree.
Prevention:
- Sanitize all input used in LDAP search filters
- Restrict search base DNs to expected organizational units
- Use LDAP access controls to limit what the application's service account can read
- Apply the principle of least privilege to the LDAP bind account
Special Characters and Encoding
LDAP filter injection relies on special characters. Proper escaping neutralizes the attack:
Characters that must be escaped in LDAP filters:
(--\28)--\29*--\2a\--\5c- NUL --
\00
Characters that must be escaped in LDAP DNs:
,+"\<>;and leading/trailing spaces
Most LDAP libraries provide escaping functions. Use them:
// Java LDAP escaping
String safeFilter = "(uid=" + Filter.encodeValue(username) + ")";
# Python ldap3 escaping
from ldap3.utils.conv import escape_filter_chars
safe_filter = f"(uid={escape_filter_chars(username)})"
// C# escaping
string safeUsername = username
.Replace("\\", "\\5c")
.Replace("(", "\\28")
.Replace(")", "\\29")
.Replace("*", "\\2a")
.Replace("\0", "\\00");
Secure LDAP Implementation
Use parameterized queries. Modern LDAP libraries support parameterized filters that handle escaping automatically:
// Java - parameterized search
SearchControls controls = new SearchControls();
String filter = "(&(uid={0})(userPassword={1}))";
NamingEnumeration<SearchResult> results =
ctx.search(baseDN, filter, new Object[]{username, password}, controls);
Validate input strictly. Usernames should be alphanumeric with limited special characters. Reject any input containing LDAP metacharacters before it reaches the query builder.
Use LDAP bind for authentication. Instead of searching for a user with a matching password, attempt to bind (authenticate) to the LDAP server as that user. This delegates authentication to the LDAP server itself, eliminating the password comparison query entirely:
// Bind as the user instead of searching for password match
Hashtable<String, String> env = new Hashtable<>();
env.put(Context.SECURITY_PRINCIPAL, "uid=" + escapeDN(username) + ",ou=users,dc=example,dc=com");
env.put(Context.SECURITY_CREDENTIALS, password);
DirContext userCtx = new InitialDirContext(env);
Use LDAPS. Always use LDAP over TLS (LDAPS on port 636 or StartTLS on port 389). Unencrypted LDAP transmits credentials in cleartext and is vulnerable to interception and modification.
Restrict service account permissions. The application's LDAP service account should have the minimum permissions required. Read-only access to specific organizational units is sufficient for most authentication use cases.
Active Directory Specific Concerns
Active Directory is the most common LDAP directory in enterprise environments and has additional attack surface:
- NTLM relay attacks can be triggered if LDAP traffic is unencrypted
- Kerberoasting targets service accounts discoverable through LDAP queries
- Password spraying uses LDAP to enumerate valid accounts before attempting authentication
- DCSync attacks require specific LDAP permissions that overly permissive service accounts might have
Harden AD-specific configurations:
- Require LDAP signing and channel binding
- Enable LDAP logging for sensitive operations
- Restrict which accounts can perform LDAP searches
- Monitor for unusual LDAP query patterns
How Safeguard.sh Helps
Safeguard.sh monitors your LDAP libraries and directory service dependencies for known injection vulnerabilities and insecure defaults. When a CVE is published for an LDAP library in your dependency tree -- whether it is a filter injection bypass, a TLS validation flaw, or an authentication weakness -- Safeguard.sh alerts your team immediately. By tracking the security posture of every component that touches your directory infrastructure, Safeguard.sh helps ensure that your LDAP integration remains secure as new vulnerabilities are discovered.