| name | ldap-injection-anti-pattern |
| description | Security anti-pattern for LDAP injection vulnerabilities (CWE-90). Use when generating or reviewing code that constructs LDAP filters, queries directory services, or handles user input in LDAP operations. Detects unescaped special characters in LDAP filters. |
LDAP Injection Anti-Pattern
Severity: High
Summary
LDAP Injection occurs when user input is insecurely inserted into LDAP queries without escaping special characters. Attackers manipulate query logic through character injection, enabling authentication bypass, unauthorized data access, privilege escalation, and directory structure disclosure.
The Anti-Pattern
Never build LDAP filters by concatenating unescaped user input. Special characters alter filter structure and meaning.
BAD Code Example
import ldap
def find_user_by_name(ldap_connection, username):
search_filter = f"(uid={username})"
try:
results = ldap_connection.search_s(
"ou=users,dc=example,dc=com",
ldap.SCOPE_SUBTREE,
search_filter
)
return results
except ldap.LDAPError as e:
print(f"LDAP search failed: {e}")
return None
GOOD Code Example
import ldap
from ldap.filter import escape_filter_chars
def find_user_by_name_safe(ldap_connection, username):
safe_username = escape_filter_chars(username)
search_filter = f"(uid={safe_username})"
try:
results = ldap_connection.search_s(
"ou=users,dc=example,dc=com",
ldap.SCOPE_SUBTREE,
search_filter
)
return results
except ldap.LDAPError as e:
print(f"LDAP search failed: {e}")
return None
def authenticate_ldap_safe(username, password):
safe_username = escape_filter_chars(username)
user_dn = f"uid={safe_username},ou=users,dc=example,dc=com"
try:
conn = ldap.initialize("ldap://ldap.example.com")
conn.simple_bind_s(user_dn, password)
conn.unbind_s()
return True
ldap.INVALID_CREDENTIALS:
ldap.LDAPError e:
()
Language-Specific Examples
Java:
import javax.naming.directory.*;
public User findUser(String username) throws NamingException {
DirContext ctx = getContext();
String filter = "(uid=" + username + ")";
SearchControls controls = new SearchControls();
NamingEnumeration<SearchResult> results =
ctx.search("ou=users,dc=example,dc=com", filter, controls);
return parseUser(results);
}
import javax.naming.directory.*;
import org.apache.directory.api.ldap.model.filter.FilterEncoder;
public User findUserSafe(String username) throws NamingException {
DirContext ctx = getContext();
String safeUsername = FilterEncoder.encodeFilterValue(username);
String filter = "(uid=" + safeUsername + ")";
SearchControls controls = new SearchControls();
NamingEnumeration<SearchResult> results =
ctx.search("ou=users,dc=example,dc=com", filter, controls);
return parseUser(results);
}
public boolean authenticateSafe(String username, String password) {
String escapedUsername = FilterEncoder.encodeFilterValue(username);
String userDn = "uid=" + escapedUsername + ",ou=users,dc=example,dc=com";
try {
DirContext ctx = new InitialDirContext(
createEnv(userDn, password));
ctx.close();
;
} (AuthenticationException e) {
;
} (NamingException e) {
(, e);
}
}
C# (ASP.NET):
using System.DirectoryServices;
public User FindUser(string username)
{
using (var entry = new DirectoryEntry("LDAP://dc=example,dc=com"))
using (var searcher = new DirectorySearcher(entry))
{
searcher.Filter = $"(sAMAccountName={username})";
SearchResult result = searcher.FindOne();
return ParseUser(result);
}
}
using System.DirectoryServices;
using System.Text.RegularExpressions;
public User FindUserSafe(string username)
{
string escapedUsername = EscapeLdapFilter(username);
using (var entry = new DirectoryEntry("LDAP://dc=example,dc=com"))
using (var searcher = new DirectorySearcher(entry))
{
searcher.Filter = $"(sAMAccountName={escapedUsername})";
SearchResult result = searcher.FindOne();
return ParseUser(result);
}
}
private string EscapeLdapFilter(string input)
{
return input
.Replace("\\", "\\5c")
.Replace("*", "\\2a")
.Replace("(", "\\28")
.Replace(")", "\\29")
.Replace("\0", "\\00");
}
Node.js (ldapjs):
const ldap = require('ldapjs');
function searchUser(username, callback) {
const client = ldap.createClient({ url: 'ldap://ldap.example.com' });
const filter = `(uid=${username})`;
client.search('ou=users,dc=example,dc=com', {
filter: filter,
scope: 'sub'
}, callback);
}
const ldap = require('ldapjs');
function searchUserSafe(username, callback) {
const client = ldap.createClient({ url: 'ldap://ldap.example.com' });
const safeUsername = ldap.filters.escape(username);
const filter = `(uid=${safeUsername})`;
client.search('ou=users,dc=example,dc=com', {
filter: filter,
scope: 'sub'
}, callback);
}
function authenticateSafe(username, password, callback) {
const client = ldap.createClient({ url: 'ldap://ldap.example.com' });
const safeUsername = ldap.filters.escape(username);
const userDn = `uid=${safeUsername},ou=users,dc=example,dc=com`;
client.bind(userDn, password, (err) => {
client.unbind();
callback(err ? false : true);
});
}
Detection
- Find unescaped LDAP filter construction: Grep for string concatenation in filters:
rg 'search.*\(.*f["\'].*{|search.*\+.*uid=' --type py
rg 'SearchRequest.*\+|DirectorySearcher.*\+' --type cs
rg 'new LdapQuery.*\+|filter.*concat' --type java
- Verify escaping function usage: Check for proper escaping:
rg 'ldap.*search' -A 2 | rg -v 'escape_filter|escape_dn' (Python)
rg 'DirectorySearcher|SearchRequest' -A 3 (C#)
- Look for missing escaping before filter construction
- Audit all LDAP operations: Find search and bind operations:
rg 'ldap\.search|search_s|search_st' --type py
rg 'DirectorySearcher|DirectoryEntry' --type cs
rg 'LdapContext\.search|DirContext\.search' --type java
- Test with LDAP metacharacters: Inject special chars to verify escaping:
* (wildcard), ( ) (filter grouping), \ (escape), | (OR), & (AND)
- Input:
admin*)(uid=*) should not bypass authentication
Prevention
Related Security Patterns & Anti-Patterns
References