LDAP injection is the same class of bug as SQL injection: untrusted input is combined with a filter or DN string without escaping, and an attacker crafts input that changes the meaning of that string instead of just supplying a value.
Imagine a login form that builds a filter like this, directly from user input:
// Vulnerable — do not do this
const filter = `(&(objectClass=user)(uid=${username}))`;
For a normal username, this works exactly as intended:
(&(objectClass=user)(uid=jdoe))- AND
objectClassequalsuseruidequalsjdoe
LDAP filter syntax uses *, (, and ) as structural characters (see LDAP filters). If username isn't escaped, an attacker can include those characters to inject additional filter clauses. Supplying a username of:
*)(uid=*))(|(uid=*
turns the intended filter into:
(&(objectClass=user)(uid=*)(uid=*))(|(uid=*)))
The carefully placed parentheses close the original uid clause early and open a new | (OR) clause that matches essentially any entry — potentially bypassing whatever the filter was meant to restrict, depending on how the application uses the result.
Danger
The exact impact depends on how the filtered results are used, but at minimum this pattern can let an attacker retrieve entries they shouldn't be able to enumerate, or bypass an intended restriction embedded in the filter.
Escape any value that isn't a hardcoded, trusted literal before it goes into a filter string. RFC 4515 escaping (also covered in LDAP filters) replaces the structural characters with their hex-escaped form:
// Safe
const filter = `(&(objectClass=user)(uid=${escapeFilterValue(username)}))`;
With escaping applied, the same malicious input becomes an inert, literal value instead of restructuring the filter — every *, (, and ) in it is replaced with its hex-escaped form (\2a, \28, \29):
(&(objectClass=user)(uid=\2a\29\28uid=\2a\29\29\28\7c\28uid=\2a))
The directory reads that whole escaped sequence as one literal uid value to compare against, not as filter syntax — so it simply matches nothing, instead of matching everything.
Never implement this escaping by hand inline — use a tested helper function, like the escapeFilterValue used throughout the Node.js section.
LDAP Filter Builder
Prototype and validate filters safely instead of hand-escaping them.
Building a DN from untrusted input has the same risk, using , and + as the structural characters instead of *(). See Distinguished names for DN escaping.
DN Parser
Parse and re-serialize DNs instead of building them by hand.
Continue to the Production checklist to review everything together before shipping.