LDAP injection happens when attacker-controlled input is concatenated into LDAP filters without proper escaping. The impact is often authentication bypass, user enumeration, or unauthorized access to directory data. These notes cover LDAP structure, filter syntax, common injection patterns, wildcard and tautology abuse, and blind LDAP extraction techniques.
LDAP is a directory access protocol used to store and query structured identity and directory information. It is common in environments such as Microsoft Active Directory and OpenLDAP.
Entries are organized hierarchically and represented as objects with attributes. This makes LDAP especially common in authentication, user management, group membership, and enterprise identity workflows.
LDAP directories are tree-based. Important concepts include:
cn=John Doe,ou=people,dc=example,dc=com.cn=John Doe.mail=john@example.com.LDAP search queries are built from a base DN, a scope, a filter, and requested attributes. The filter is the key part from an injection perspective.
(cn=John Doe)
(cn=J*)
(&(objectClass=user)(|(cn=John*)(cn=Jane*)))
The logical operators are especially important:
& for AND| for OR! for NOT* as a wildcardLDAP injection is conceptually similar to SQL injection. The vulnerable application takes user input, concatenates it into an LDAP filter, and sends the final query to the directory server. If the input is not escaped correctly, the attacker can alter the intended logic.
Typical outcomes include:
A classic example is an application that builds its LDAP authentication filter directly from POST parameters:
$username = $_POST['username'];
$password = $_POST['password'];
$filter = "(&(uid=$username)(userPassword=$password))";
$search_result = ldap_search($ldap_conn, $ldap_dn, $filter);
If $username or $password are not escaped, the attacker can inject LDAP filter syntax instead of a literal value.
The wildcard * matches any sequence of characters. If the application accepts it unchecked inside an authentication filter, it may match any existing value.
Injected input:
username=*&password=*
Resulting filter:
(&(uid=*)(userPassword=*))
That can match any user with a uid and any user with a userPassword attribute, depending on how the application interprets the result set.
LDAP injection can also abuse logical operators to create conditions that are always true.
Suppose attacker input reshapes the filter like this:
(&(uid=*)(|(&)(userPassword=pwd)))
The expression (&) is effectively treated as a trivially true condition in this context, so the OR branch becomes true regardless of whether the submitted password is valid. This can bypass the intended password check.
Core idea: the attacker is not guessing the password. They are rewriting the filter so the password condition no longer matters.
Using just * may return the first matching user, but wildcard-style injection can often be tuned. For example, using f* as the username forces the filter toward entries whose uid begins with f.
This turns a simple auth bypass into a way to steer which account is selected by the application.
Blind LDAP injection appears when the application does not return full query results but still behaves differently depending on whether the filter matches. The attacker then infers information from success, failure, redirect differences, or wording changes in the response.
Example vulnerable pattern:
$filter = "(&(uid=$username)(userPassword=$password))";
$search_result = ldap_search($ldap_conn, $ldap_dn, $filter);
if ($entries['count'] > 0) {
if ($entry['uid'][0] === $_POST['username']) {
$message = "Welcome, " . $entry['cn'][0] . "!\n";
} else {
$message = "Something is wrong in your password.\n";
}
}
Those subtle differences are enough to leak information one character at a time.
An attacker can inject partial username conditions and watch whether the application behaves as if a matching entry exists.
Example payload structure:
a*)(|(&
URL-encoded inside a request, that can reshape the filter into something like:
(&(uid=a*)(|(&)(userPassword=pwd)))
If the application returns a different message, the attacker learns that at least one username beginning with a exists. Then they move to ab*, ac*, and so on until the full identifier is recovered.
Blind extraction is repetitive, so it is a good candidate for scripting. A loop can iterate through a candidate character set, submit payloads, and watch for the response indicator that means “prefix exists”.
Typical Python approach:
* is often enough to reveal weak filter handling.*, (, ), \, and null bytes.