LDAP Injection¶
What does this mean ?¶
LDAP injection occurs when untrusted data changes the structure of a directory search filter or distinguished name (DN). A filter value and a DN use different escaping rules. An encoder intended for one context must not be used blindly in the other.
What can happen ?¶
An altered filter can broaden a search or bypass an application's assumed selection criteria. The directory service account's permissions and requested attributes determine the data exposed. Injection prevention does not justify giving the account unrestricted directory access.
Recommendation¶
Use the directory library's filter parameter API or its documented filter-value encoder. Keep attribute names, search bases and query structure under application control. Bound search scope, result count, duration and returned attributes. Use a least-privileged directory account over a protected connection.
Do not search userPassword attributes to implement login or restrict passwords to letters to avoid escaping. Authentication should use the identity provider or a reviewed bind flow, with transport security and proper handling of failed/empty credentials. This page demonstrates a name lookup only.
Sample Code¶
The lookup finds a person by their cn attribute. name is a bounded string, and the existing directory connection, base DN and access policy are server-owned. The unsafe pattern is "(cn=" + name + ")" without an encoder.
JNDI supports filter arguments through the search overload:
var controls = new javax.naming.directory.SearchControls();
controls.setSearchScope(javax.naming.directory.SearchControls.ONELEVEL_SCOPE);
controls.setCountLimit(10);
controls.setTimeLimit(3000);
controls.setReturningAttributes(new String[] { "cn" });
var results = context.search(
"ou=people,dc=example,dc=com",
"(&(objectClass=person)(cn={0}))", new Object[] { name }, controls);
try {
while (results.hasMore()) {
var person = results.next();
// Apply application access policy before exposing the result.
}
} finally {
results.close();
}
Use the LDAP extension's context-specific encoder:
$escaped = ldap_escape($name, '', LDAP_ESCAPE_FILTER);
$filter = '(&(objectClass=person)(cn=' . $escaped . '))';
$result = ldap_list($connection, 'ou=people,dc=example,dc=com',
$filter, ['cn'], 0, 10, 3);
if ($result === false) throw new RuntimeException('Directory lookup failed');
LDAP_ESCAPE_DN is for DN components, not this filter. Do not replace the built-in encoder with a sequence of ad hoc substitutions.
ldap3 dependency, with an already authenticated and protected connection:
from ldap3 import LEVEL
from ldap3.utils.conv import escape_filter_chars
def find_person(connection, name: str):
if not isinstance(name, str) or not 1 <= len(name) <= 100:
raise ValueError("Invalid name")
query = "(&(objectClass=person)(cn=" + escape_filter_chars(name) + "))"
return connection.search(
"ou=people,dc=example,dc=com", query,
search_scope=LEVEL, attributes=["cn"], size_limit=10, time_limit=3,
)
Inspect the library's operation result and returned entries under the application's access policy; the boolean is not an authentication result.
DirectorySearcher.Filter is a string API. The following encoder hex-escapes every UTF-8 octet, which RFC4515 permits for filter assertion values:
static string EscapeFilterValue(string value)
{
var result = new System.Text.StringBuilder();
foreach (byte octet in System.Text.Encoding.UTF8.GetBytes(value))
result.Append('\\').Append(octet.ToString("x2"));
return result.ToString();
}
using var searcher = new System.DirectoryServices.DirectorySearcher(searchRoot);
searcher.Filter = "(&(objectClass=person)(cn=" + EscapeFilterValue(name) + "))";
searcher.SearchScope = System.DirectoryServices.SearchScope.OneLevel;
searcher.SizeLimit = 10;
searcher.ServerTimeLimit = TimeSpan.FromSeconds(3);
searcher.PropertiesToLoad.Add("cn");
using var results = searcher.FindAll();
searchRoot must be configured with a reviewed secure bind. This encoder is not a DN encoder.
Regression checks¶
Use an isolated directory fixture or a filter parser stub. Verify that an ordinary name, a name containing parentheses, a literal asterisk, backslash, NUL and Unicode are represented as one value. Confirm unknown attribute/search-base inputs cannot change the query. Test size/time limits and ensure search errors do not become successful authentication.