LDAP Injection: Complete Code Review Guide
Table of Contents
1. Introduction to LDAP Injection
LDAP (Lightweight Directory Access Protocol) injection is a vulnerability that targets applications which construct LDAP queries from user-supplied input. LDAP directories are the backbone of enterprise identity management — Active Directory, OpenLDAP, and FreeIPA store user accounts, group memberships, organizational hierarchies, and access policies for millions of organizations worldwide.
Enterprise Impact
LDAP injection is particularly dangerous because LDAP directories often serve as the single source of truth for authentication and authorization across an entire organization. A successful attack can compromise not just one application, but every system that relies on the directory — email, VPN, internal tools, cloud services, and more.
When applications build LDAP search filters or Distinguished Names (DNs) by concatenating user input without proper escaping, attackers can inject LDAP metacharacters to manipulate query logic. This can lead to authentication bypass, unauthorized data access, privilege escalation, and information disclosure about the directory structure.
LDAP injection is often overlooked in modern security assessments because it targets legacy enterprise infrastructure. However, LDAP directories remain critical in corporate environments, and many modern applications still interact with Active Directory or OpenLDAP for SSO, user provisioning, and group-based access control.
LDAP Injection Attack Flow
user=*)(|(&Filter constructionManipulated queryAuth bypass / data leak1. Filter Injection
Attacker injects LDAP filter metacharacters (*, (, ), |, &, !) to manipulate search queries and bypass authentication logic.
2. DN Injection
Attacker manipulates Distinguished Name components to traverse the directory tree or bind as a different user.
3. Blind Extraction
Through boolean-based or error-based techniques, attackers extract sensitive directory attributes like passwords and group memberships.
Why is LDAP injection considered particularly dangerous in enterprise environments?
2. Real-World Scenario
Consider a corporate intranet application that authenticates users against Active Directory. The login form sends a username and password, and the backend constructs an LDAP search filter to find the user:
1// Vulnerable Java LDAP authentication
2public boolean authenticate(String username, String password) {
3 // VULNERABLE: User input directly concatenated into LDAP filter
4 String filter = "(&(uid=" + username + ")(userPassword=" + password + "))";
5
6 SearchControls controls = new SearchControls();
7 controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
8
9 NamingEnumeration<?> results = ctx.search(
10 "ou=users,dc=company,dc=com",
11 filter,
12 controls
13 );
14
15 return results.hasMore();
16}This looks similar to SQL injection — user input is concatenated directly into a query string. An attacker can exploit this by injecting LDAP filter metacharacters:
1# Normal login:
2Username: jsmith
3Password: secret123
4Filter: (&(uid=jsmith)(userPassword=secret123))
5
6# Attack 1: Authentication bypass with wildcard
7Username: *
8Password: *
9Filter: (&(uid=*)(userPassword=*))
10→ Matches ANY user with ANY password — returns first user (often admin)
11
12# Attack 2: Authentication bypass with filter termination
13Username: admin)(|(&
14Password: anything)
15Filter: (&(uid=admin)(|(&)(userPassword=anything)))
16→ The injected (|(& creates an always-true condition
17
18# Attack 3: Bypass password check entirely
19Username: admin)(&)
20Password: anything
21Filter: (&(uid=admin)(&)(userPassword=anything))
22→ (&) is always true in many LDAP implementationsAuthentication Bypass
By injecting the wildcard character (*) as both username and password, the LDAP filter becomes (&(uid=*)(userPassword=*)) which matches every user in the directory. The application returns the first result — typically the administrator account — granting the attacker full admin access without knowing any credentials.
The attack can be more targeted. An attacker can use LDAP injection to log in as a specific user:
1# Log in as CEO without knowing password
2Username: ceo)(|(uid=*
3Password: anything)
4Filter: (&(uid=ceo)(|(uid=*)(userPassword=anything)))
5
6# This creates an OR condition that matches uid=* (everything)
7# Since LDAP returns the first match and uid=ceo is the first condition,
8# the LDAP server may return the CEO's account
9
10# Even simpler on some implementations:
11Username: ceo)(%00
12Password: anything
13Filter: (&(uid=ceo) (userPassword=anything))
14→ Null byte truncates the filter — password check is ignoredAn application uses the filter (&(cn=USER_INPUT)(objectClass=person)). An attacker enters *)(objectClass=*. What is the resulting filter?
3. Understanding LDAP & Filter Syntax
To effectively identify LDAP injection during code review, you need to understand LDAP's data model and query syntax. LDAP organizes data in a hierarchical tree structure called a Directory Information Tree (DIT), and uses search filters (defined in RFC 4515) to query entries.
LDAP Directory Structure:
1# Example Active Directory hierarchy
2dc=company,dc=com ← Root (Domain Component)
3├── ou=Users ← Organizational Unit
4│ ├── cn=John Smith ← Common Name (user entry)
5│ │ ├── uid=jsmith ← User ID
6│ │ ├── mail=jsmith@company.com ← Email
7│ │ ├── memberOf=cn=Admins,... ← Group membership
8│ │ └── userPassword={SSHA}... ← Hashed password
9│ ├── cn=Jane Doe
10│ └── cn=Bob Wilson
11├── ou=Groups
12│ ├── cn=Admins
13│ ├── cn=Developers
14│ └── cn=Finance
15├── ou=ServiceAccounts
16│ └── cn=ldap-reader
17└── ou=Computers
18 └── cn=WORKSTATION01Distinguished Names (DNs):
1# Every LDAP entry has a unique Distinguished Name (DN)
2# DNs are built from most-specific to least-specific (left to right)
3
4cn=John Smith,ou=Users,dc=company,dc=com ← Full DN for a user
5cn=Admins,ou=Groups,dc=company,dc=com ← Full DN for a group
6
7# DN components:
8# cn = Common Name (leaf entry)
9# ou = Organizational Unit (container)
10# dc = Domain Component (domain)
11# uid = User ID (sometimes used instead of cn)
12# o = Organization
13# c = Country
14
15# DNs have their own special characters that must be escaped:
16# , + " \ < > ; = (space at start/end)LDAP Filter Syntax Reference
Filter Operators
(attr=value)Equality match(attr=*)Presence test (wildcard)(attr=val*)Substring match (prefix)(attr>=value)Greater or equal(attr<=value)Less or equal(attr~=value)Approximate matchLogical Combinators
(&(f1)(f2))AND — both filters must match(|(f1)(f2))OR — either filter must match(!(filter))NOT — negates the filter(&(|(f1)(f2))(f3))Nested — complex logic combinationsInjection Metacharacters
*()\|&!NULLDAP Search Filter Examples:
1# Simple equality filter
2(uid=jsmith)
3
4# AND filter — user must match all conditions
5(&(uid=jsmith)(objectClass=person)(!(disabled=TRUE)))
6
7# OR filter — user matches any condition
8(|(uid=jsmith)(mail=jsmith@company.com))
9
10# Wildcard / substring filters
11(cn=John*) ← Starts with "John"
12(cn=*Smith) ← Ends with "Smith"
13(cn=*admin*) ← Contains "admin"
14
15# Nested logic
16(&(objectClass=person)(|(department=Engineering)(department=Security)))
17
18# Common authentication filter (the one most vulnerable to injection)
19(&(uid=USERNAME)(userPassword=PASSWORD))
20
21# Active Directory specific
22(&(sAMAccountName=USERNAME)(objectCategory=person)(objectClass=user))LDAP Filter RFC 4515 Escaping
RFC 4515 specifies that the following characters must be escaped in LDAP filter values using their hex ASCII codes: * → \2a, ( → \28, ) → \29, \ → \5c, NUL → \00. Failure to properly escape these characters is the root cause of LDAP filter injection.
What does the LDAP filter (&(objectClass=user)(memberOf=cn=Admins,ou=Groups,dc=corp,dc=com)) do?
4. LDAP Filter Injection
LDAP filter injection occurs when user input is concatenated into LDAP search filters without proper escaping. The attacker injects metacharacters to alter the filter's logical structure. Let's examine the major attack patterns:
Pattern 1: OR-Based Authentication Bypass
1# Original filter template:
2(&(uid=USER)(userPassword=PASS))
3
4# Attacker input — username: admin)(|(uid=*
5# Resulting filter:
6(&(uid=admin)(|(uid=*)(userPassword=PASS)))
7
8# How LDAP evaluates this:
9# (& ← AND of two conditions
10# (uid=admin) ← First: uid must be "admin"
11# (|(uid=*)(userPassword=PASS)) ← Second: uid=* (always true) OR password check
12# )
13# Since uid=* is always true, the OR is always true
14# Only uid=admin needs to match → password is bypassed!Pattern 2: Wildcard-Based User Enumeration
1// Vulnerable user search endpoint
2public List<User> searchUsers(String searchTerm) {
3 // VULNERABLE: Direct concatenation
4 String filter = "(cn=" + searchTerm + ")";
5
6 NamingEnumeration<?> results = ctx.search(
7 "ou=Users,dc=company,dc=com",
8 filter,
9 controls
10 );
11 // ...
12}
13
14// Normal use: searchTerm = "John" → (cn=John)
15// Attack: searchTerm = "*" → (cn=*) — returns ALL users!
16// Attack: searchTerm = "a*" → (cn=a*) — enumerate users starting with 'a'
17
18// More advanced enumeration:
19// searchTerm = "*)(uid=*"
20// Filter: (cn=*)(uid=*) — may return additional attributesPattern 3: Attribute Disclosure via Injected Conditions
1# Original filter for a lookup endpoint:
2(uid=USER_INPUT)
3
4# Attack: Discover which users are admins
5# Input: *)(memberOf=cn=Admins,ou=Groups,dc=company,dc=com
6# Filter: (uid=*)(memberOf=cn=Admins,ou=Groups,dc=company,dc=com)
7# Returns all users who are in the Admins group
8
9# Attack: Find users with specific attributes
10# Input: *)(telephoneNumber=*
11# Filter: (uid=*)(telephoneNumber=*)
12# Returns all users who have a phone number set
13
14# Attack: Discover service accounts
15# Input: *)(objectClass=serviceAccount
16# Filter: (uid=*)(objectClass=serviceAccount)
17# Returns all service accounts in the directoryPattern 4: Null Byte Truncation (Legacy)
1# Some older LDAP implementations and language bindings
2# are vulnerable to null byte truncation
3
4# Original filter:
5(&(uid=USER)(userPassword=PASS))
6
7# Null byte injection — username: admin)%00
8# In some implementations, %00 terminates the string:
9(&(uid=admin) (userPassword=PASS))
10# Everything after the null byte is ignored
11# Effective filter: (&(uid=admin))
12# → Returns admin user without password check!
13
14# URL encoding variants:
15# admin)%00 → null byte via URL encoding
16# admin) → null byte via hex encoding
17# admin)\00 → LDAP hex escape for null
18
19# NOTE: This mostly affects older Java LDAP, PHP ldap_search(),
20# and C-based LDAP libraries. Modern implementations are generally safe.LDAP Injection Complexity
Unlike SQL injection, LDAP filter injection must produce valid filter syntax. Parentheses must be balanced, and the overall structure must be parseable. This means payloads often need to carefully close existing parentheses and open new ones. Attackers typically need to understand the exact filter template to craft working exploits.
A filter template is (&(sAMAccountName=USER)(objectClass=user)). An attacker enters: *)(objectClass=computer)(&(sAMAccountName=*. What does the filter become?
5. Finding Vulnerable Sinks
During code review, focus on finding locations where LDAP queries are constructed with user input. Here are the key sinks across major programming languages and frameworks.
Java / JNDI (Java Naming and Directory Interface):
1// JNDI — The most common Java LDAP interface
2import javax.naming.directory.*;
3
4// CRITICAL SINKS:
5DirContext ctx = new InitialDirContext(env);
6
7// 1. search() with string filter — PRIMARY INJECTION VECTOR
8ctx.search("ou=Users,dc=com", filterString, searchControls);
9ctx.search(baseDN, filterString, filterArgs, searchControls);
10
11// 2. search() with SearchControls
12SearchControls sc = new SearchControls();
13sc.setSearchScope(SearchControls.SUBTREE_SCOPE);
14ctx.search(baseDNFromInput, filter, sc); // Base DN injection too!
15
16// 3. lookup() and lookupLink()
17ctx.lookup("cn=" + userInput + ",ou=Users,dc=com"); // DN injection
18
19// 4. bind() — authentication
20ctx.addToEnvironment(Context.SECURITY_PRINCIPAL,
21 "cn=" + username + ",ou=Users,dc=com"); // DN injection
22ctx.addToEnvironment(Context.SECURITY_CREDENTIALS, password);
23
24// Spring LDAP
25LdapTemplate ldapTemplate;
26ldapTemplate.search(baseDN, filterString, mapper); // Filter injection
27ldapTemplate.lookup(dnFromInput); // DN injection
28ldapTemplate.findOne(query, User.class); // Check query sourcePython LDAP Libraries:
1# python-ldap
2import ldap
3
4conn = ldap.initialize('ldap://server:389')
5
6# CRITICAL SINKS:
7# 1. search_s() / search_st() with filter string
8conn.search_s(
9 'ou=Users,dc=company,dc=com', # Base DN (also injectable)
10 ldap.SCOPE_SUBTREE,
11 f'(uid={username})', # VULNERABLE: f-string filter
12 ['cn', 'mail'] # Attributes to return
13)
14
15# 2. search_ext_s() with filter
16conn.search_ext_s(base_dn, scope, filter_str)
17
18# 3. simple_bind_s() with DN from user input
19conn.simple_bind_s(
20 f'cn={username},ou=Users,dc=com', # VULNERABLE DN
21 password
22)
23
24# 4. compare_s() with DN injection
25conn.compare_s(
26 f'cn={username},ou=Users,dc=com', # VULNERABLE DN
27 'userPassword',
28 password
29)
30
31# ldap3 library
32from ldap3 import Connection, Server, ALL
33
34conn = Connection(server, user=user_dn, password=password)
35conn.search(
36 'ou=Users,dc=com',
37 f'(uid={user_input})', # VULNERABLE filter
38 attributes=['cn', 'mail']
39)PHP LDAP Functions:
1<?php
2// PHP has built-in LDAP extension functions
3
4// CRITICAL SINKS:
5// 1. ldap_search() — the primary injection vector
6$result = ldap_search(
7 $conn,
8 "ou=Users,dc=company,dc=com",
9 "(uid=" . $_POST['username'] . ")", // VULNERABLE
10 ["cn", "mail"]
11);
12
13// 2. ldap_list() — single-level search
14$result = ldap_list($conn, $base_dn, $filter_from_input);
15
16// 3. ldap_read() — read specific entry
17$result = ldap_read($conn, $dn_from_input, "(objectClass=*)");
18
19// 4. ldap_bind() — authentication with DN injection
20$bind_dn = "cn=" . $_POST['username'] . ",ou=Users,dc=com";
21ldap_bind($conn, $bind_dn, $_POST['password']);
22
23// 5. ldap_compare() — attribute comparison
24ldap_compare($conn, $dn_from_input, "userPassword", $password);
25
26// NOTE: PHP does have ldap_escape() (since PHP 5.6)
27// but many codebases don't use it.NET / C# LDAP:
1// System.DirectoryServices (Active Directory)
2using System.DirectoryServices;
3
4// CRITICAL SINKS:
5// 1. DirectorySearcher with filter string
6var searcher = new DirectorySearcher(entry);
7searcher.Filter = $"(&(sAMAccountName={username})(objectClass=user))";
8// VULNERABLE: filter built from user input
9var result = searcher.FindOne();
10
11// 2. DirectoryEntry path construction
12var entry = new DirectoryEntry(
13 $"LDAP://cn={username},ou=Users,dc=company,dc=com" // DN injection
14);
15
16// 3. System.DirectoryServices.Protocols
17var searchRequest = new SearchRequest(
18 baseDn,
19 $"(uid={userInput})", // VULNERABLE filter
20 SearchScope.Subtree,
21 attributeList
22);
23var response = (SearchResponse)connection.SendRequest(searchRequest);
24
25// 4. PrincipalSearcher (higher level, often safer)
26var ctx = new PrincipalContext(ContextType.Domain);
27var searcher = new PrincipalSearcher(
28 new UserPrincipal(ctx) { SamAccountName = userInput }
29);
30// Generally safer — but check if input reaches raw filterLDAP Injection Sink Severity by Language
| Language | Library/API | Primary Sink | Common In |
|---|---|---|---|
| Java | JNDI / javax.naming | ctx.search(base, filterString, ...) | Enterprise apps, Spring |
| Java | Spring LDAP | ldapTemplate.search() | Spring Boot applications |
| Python | python-ldap | conn.search_s(base, scope, filter) | Flask, Django apps |
| Python | ldap3 | conn.search(base, filter) | Modern Python apps |
| PHP | ldap extension | ldap_search($conn, $base, $filter) | Legacy CMS, WordPress plugins |
| C# | DirectoryServices | DirectorySearcher.Filter = str | .NET enterprise apps |
| Node.js | ldapjs | client.search(base, {filter: str}) | Express.js apps |
| Go | go-ldap | conn.Search(searchRequest) | Go microservices |
During code review, you see: ldap_search($conn, $base, '(cn=' . $input . ')'); in PHP. What should you check?
6. Distinguished Name Injection
Besides filter injection, LDAP applications are also vulnerable to Distinguished Name (DN) injection. This occurs when user input is used to construct the base DN for searches, the bind DN for authentication, or paths for entry lookups. DN injection has different metacharacters and escaping rules than filter injection.
DN Injection in Authentication (Bind):
1// Vulnerable: Building bind DN from user input
2public boolean authenticate(String username, String password) {
3 Hashtable<String, String> env = new Hashtable<>();
4 env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
5 env.put(Context.PROVIDER_URL, "ldap://ad.company.com:389");
6
7 // VULNERABLE: DN built from user input
8 String bindDN = "cn=" + username + ",ou=Users,dc=company,dc=com";
9 env.put(Context.SECURITY_PRINCIPAL, bindDN);
10 env.put(Context.SECURITY_CREDENTIALS, password);
11
12 try {
13 new InitialDirContext(env); // Bind attempt
14 return true;
15 } catch (AuthenticationException e) {
16 return false;
17 }
18}
19
20// Attack: username = admin,ou=Users,dc=company,dc=com
21// Bind DN becomes: cn=admin,ou=Users,dc=company,dc=com,ou=Users,dc=company,dc=com
22// → Invalid DN, but some LDAP servers are lenient
23
24// More dangerous attack:
25// username = admin,ou=ServiceAccounts
26// Bind DN: cn=admin,ou=ServiceAccounts,ou=Users,dc=company,dc=com
27// → Attempts to bind as a service account instead of a user!DN Injection in Base DN:
1# Vulnerable: User controls the base DN for search
2@app.route('/api/users/<department>')
3def get_department_users(department):
4 # VULNERABLE: Department name used in base DN
5 base_dn = f'ou={department},ou=Users,dc=company,dc=com'
6
7 result = conn.search_s(
8 base_dn,
9 ldap.SCOPE_SUBTREE,
10 '(objectClass=person)',
11 ['cn', 'mail', 'title']
12 )
13
14 return jsonify([entry for dn, entry in result])
15
16# Normal use: /api/users/Engineering
17# Base DN: ou=Engineering,ou=Users,dc=company,dc=com
18
19# Attack: /api/users/Engineering,dc=company,dc=com??sub?(objectClass=*)
20# May cause the search to start from a different location in the tree
21
22# Attack: /api/users/ServiceAccounts
23# Base DN: ou=ServiceAccounts,ou=Users,dc=company,dc=com
24# If ou=ServiceAccounts exists at this path, leaks service account infoDN Special Characters:
1# DN has DIFFERENT special characters than LDAP filters
2# RFC 4514 specifies these must be escaped in DN values:
3
4, (comma) → \, — Separates DN components
5+ (plus) → \+ — Separates multi-valued RDN components
6" (quote) → \" — Quoting character
7\ (backslash) → \\ — Escape character
8< (less than) → \< — Distinguished encoding
9> (greater) → \> — Distinguished encoding
10; (semicolon) → \; — Alternative separator
11= (equals) → \= — Attribute=value separator
12 (space at start/end) → \20 — Leading/trailing spaces
13
14# Example: Properly escaped DN value
15# Input: John "Admin" Smith, Jr.
16# Escaped: cn=John \"Admin\" Smith\, Jr.,ou=Users,dc=comTwo Different Escaping Contexts
LDAP has TWO different escaping rules: one for filter values (RFC 4515: escape *, (, ), \, NUL) and one for DN values (RFC 4514: escape ,, +, ", \, <, >, ;). Using the wrong escaping function is a common mistake — always use the correct function for the context (filter escaping for filters, DN escaping for DNs).
An application builds a bind DN as: 'uid=' + username + ',ou=Users,dc=corp,dc=com'. An attacker enters admin,ou=Admins as the username. What happens?
7. Prevention Techniques
Preventing LDAP injection requires proper input escaping, using safe API methods, and implementing defense-in-depth controls. Unlike SQL, LDAP does not have parameterized queries in all languages — so escaping is often the primary defense.
1. LDAP Filter Escaping (RFC 4515):
1// Java — Use JNDI's built-in filter escaping
2import javax.naming.directory.*;
3
4// SAFE: Use search() with filter arguments (parameterized!)
5String filter = "(&(uid={0})(userPassword={1}))";
6Object[] filterArgs = { username, password };
7
8ctx.search("ou=Users,dc=company,dc=com", filter, filterArgs, controls);
9// JNDI escapes metacharacters in {0}, {1} automatically!
10// This is the Java equivalent of parameterized SQL queries.
11
12// Alternative: Manual escaping function
13public static String escapeFilterValue(String value) {
14 StringBuilder sb = new StringBuilder();
15 for (char c : value.toCharArray()) {
16 switch (c) {
17 case '*': sb.append("\\2a"); break;
18 case '(': sb.append("\\28"); break;
19 case ')': sb.append("\\29"); break;
20 case '\\': sb.append("\\5c"); break;
21 case '\0': sb.append("\\00"); break;
22 default: sb.append(c);
23 }
24 }
25 return sb.toString();
26}
27
28// Usage:
29String safeFilter = "(&(uid=" + escapeFilterValue(username) + ")" +
30 "(userPassword=" + escapeFilterValue(password) + "))";2. Spring LDAP (Recommended for Java):
1// Spring LDAP provides built-in filter encoding
2import org.springframework.ldap.filter.*;
3import org.springframework.ldap.support.LdapEncoder;
4
5// SAFE: Use Spring's filter builder classes
6AndFilter filter = new AndFilter();
7filter.and(new EqualsFilter("uid", username)); // Auto-escaped
8filter.and(new EqualsFilter("userPassword", password)); // Auto-escaped
9
10ldapTemplate.search("ou=Users", filter.encode(), mapper);
11
12// SAFE: LdapEncoder for manual escaping
13String safeValue = LdapEncoder.filterEncode(userInput);
14String safeFilter = "(uid=" + safeValue + ")";
15
16// SAFE: DN encoding
17String safeDN = LdapEncoder.nameEncode(userInput);
18String dn = "cn=" + safeDN + ",ou=Users,dc=company,dc=com";
19
20// SAFE: Use LdapQueryBuilder (Spring LDAP 2.x+)
21LdapQuery query = LdapQueryBuilder.query()
22 .base("ou=Users")
23 .where("uid").is(username) // Automatically escaped
24 .and("objectClass").is("person");
25
26ldapTemplate.search(query, mapper);3. Python Prevention:
1# python-ldap — Use ldap.filter.escape_filter_chars()
2import ldap
3from ldap.filter import escape_filter_chars
4from ldap.dn import escape_dn_chars
5
6# SAFE: Escape filter values
7safe_username = escape_filter_chars(username)
8filter_str = f'(uid={safe_username})'
9
10conn.search_s('ou=Users,dc=com', ldap.SCOPE_SUBTREE, filter_str)
11
12# SAFE: Escape DN values
13safe_cn = escape_dn_chars(username)
14bind_dn = f'cn={safe_cn},ou=Users,dc=company,dc=com'
15
16conn.simple_bind_s(bind_dn, password)
17
18# ldap3 library — Built-in escaping
19from ldap3.utils.conv import escape_filter_chars as l3_escape
20
21safe_input = l3_escape(user_input)
22conn.search('ou=Users,dc=com', f'(uid={safe_input})', attributes=['cn'])
23
24# Custom validation + escaping
25import re
26
27def validate_username(username: str) -> str:
28 """Validate and sanitize username for LDAP queries."""
29 if not isinstance(username, str):
30 raise ValueError('Username must be a string')
31 if not re.match(r'^[a-zA-Z0-9._-]+$', username):
32 raise ValueError('Username contains invalid characters')
33 return escape_filter_chars(username)4. PHP Prevention:
1<?php
2// PHP 5.6+ — Use ldap_escape()
3
4// SAFE: Escape for filter context
5$safe_username = ldap_escape($username, '', LDAP_ESCAPE_FILTER);
6$filter = "(uid=$safe_username)";
7$result = ldap_search($conn, $base_dn, $filter);
8
9// SAFE: Escape for DN context (different special chars!)
10$safe_cn = ldap_escape($username, '', LDAP_ESCAPE_DN);
11$bind_dn = "cn=$safe_cn,ou=Users,dc=company,dc=com";
12ldap_bind($conn, $bind_dn, $password);
13
14// IMPORTANT: Use the correct flag!
15// LDAP_ESCAPE_FILTER — escapes *, (, ), \, NUL
16// LDAP_ESCAPE_DN — escapes ,, +, ", \, <, >, ;, =, spaces
17// Using the wrong one leaves the application vulnerable!
18
19// Input validation before escaping (belt + suspenders)
20function validateUsername(string $username): string {
21 if (!preg_match('/^[a-zA-Z0-9._-]+$/', $username)) {
22 throw new InvalidArgumentException('Invalid username');
23 }
24 return ldap_escape($username, '', LDAP_ESCAPE_FILTER);
25}5. .NET / C# Prevention:
1// .NET — No built-in LDAP escaping until .NET 8!
2// You must implement your own or use a library
3
4// Custom filter escaping (RFC 4515)
5public static string EscapeLdapFilterValue(string value)
6{
7 var sb = new StringBuilder();
8 foreach (char c in value)
9 {
10 switch (c)
11 {
12 case '*': sb.Append("\2a"); break;
13 case '(': sb.Append("\28"); break;
14 case ')': sb.Append("\29"); break;
15 case '\': sb.Append("\5c"); break;
16 case '