LDAP Injection: Filter Manipulation & Auth Bypass
How LDAP injection lets attackers rewrite directory search filters to bypass logins and read data, why it happens, and how to bind and query safely.
Many applications still keep their users, groups, and permissions in a directory service, and they reach that directory with LDAP. When a login form or a search box builds an LDAP query by pasting user input straight into a filter string, the same problem that gives you SQL injection appears in a new place. An attacker who understands the filter syntax can rewrite the query the server runs, and the payoff ranges from reading directory attributes to logging in as someone else without a password.
LDAP injection is less famous than SQL injection, which works in the attacker's favor. Directory-backed login and lookup features often get less scrutiny, and the escaping rules are unfamiliar to developers who rarely touch LDAP.
How LDAP filters actually work
LDAP search filters use prefix notation and lean heavily on parentheses. A filter that finds a user by name looks like this:
(uid=jsmith)
Boolean logic wraps around individual terms rather than sitting between them. An AND of two conditions is written with the operator first:
(&(uid=jsmith)(objectClass=person))
An OR uses | in the same position, and ! negates. The asterisk * is a wildcard that matches any value. Those characters, along with the backslash and the NUL byte, are the structural grammar of a filter. If user input can carry any of them into the string unescaped, the input stops being data and becomes part of the query.
The core vulnerability
Picture a login that checks credentials against a directory. The application takes the submitted username and password and builds a filter to find a matching entry:
(&(uid=USERNAME)(userPassword=PASSWORD))
The intent is clear: find the entry where the uid matches and the password matches. Now suppose the username field is concatenated in without escaping and an attacker submits *)(uid=*))(|(uid=* as the username and anything as the password. The filter the server receives becomes a different query entirely, one where the appended (uid=*) clause matches every entry in the directory. The password condition is neutralized by the injected structure, and the search returns a valid user. If the application treats "a matching entry was found" as "the login succeeded," the attacker is now authenticated.
A simpler variant targets applications that only check for the existence of a matching entry. Submitting a username of * turns (uid=*) into a filter that matches every account. Where the code logs the user in as the first returned entry, that is an instant bypass.
A recurring root cause is treating "the LDAP search returned at least one entry" as proof of a correct password. Once the filter can be manipulated to always return an entry, that assumption collapses. Authentication must depend on a successful bind with the supplied credentials, not on whether a search found something.
Reading data through injection
Authentication bypass is the loud case. The quieter one is data disclosure. Any feature that searches the directory from user input (a people finder, an address book lookup, a group membership check) can be steered to return more than intended.
If a search builds (cn=SEARCHTERM) and an attacker supplies *)(objectClass=*, the resulting filter can match every object class, dumping entries the interface was never meant to surface. Adding conditions with & and |, or injecting wildcards into attribute matches, lets an attacker enumerate usernames, email addresses, group memberships, and any attribute the bind account is allowed to read.
Blind LDAP injection
When the application shows no direct output, the directory still answers indirectly. Blind LDAP injection extracts data one character at a time by asking true-or-false questions through the filter. An attacker crafts a filter that only returns a result when a target attribute starts with a guessed character, for example testing (&(uid=admin)(userPassword=a*)) and watching whether the page behaves as "found" or "not found." Walking the alphabet position by position reconstructs the value. It is slow, it is scriptable, and it works with no visible error messages at all. The technique mirrors blind SQL injection and the same automation tooling logic applies.
Where LDAP injection hides
| Feature | Why it is exposed |
|---|---|
| Directory-backed login forms | Username and password flow into a search filter |
| User and contact search | Free-text input concatenated into (cn=...) style filters |
| Group and role lookups | Membership checks build filters from a supplied identity |
| Self-service password reset | Email or username used to locate an account entry |
| Admin consoles over LDAP | Bulk queries assembled from operator-supplied fields |
Any of these is worth a close review wherever the directory query is built by string concatenation.
Why blocklisting input fails
The instinct is to strip "dangerous" characters like parentheses and asterisks. This breaks on two fronts. Legitimate input often contains those characters, so a blocklist creates false rejections and support tickets. And a blocklist that misses one metacharacter, or mishandles the backslash escape itself, leaves the door open. The backslash is especially treacherous because it is both a literal character users type and the escape character in the filter grammar. Naive filtering that removes parentheses but not backslashes can be defeated by feeding in an escape sequence the parser later expands.
How to defend against LDAP injection
The controls that hold up treat filter input the way you already treat SQL input: as data that must be neutralized before it can touch the query grammar.
- Escape every filter metacharacter. Encode the LDAP filter special characters (
*,(,),\, and NUL) using their backslash-hex form as defined in RFC 4515 before placing user input in a filter. Use your platform's dedicated encoder rather than a hand-rolled replacement. This is the primary control and it is the LDAP equivalent of parameterization. - Escape distinguished-name input separately. Values placed in a DN follow the different escaping rules of RFC 4514. Do not reuse filter escaping for DN construction; the special-character set is not the same.
- Authenticate with a bind, not a search result. Verify credentials by attempting an LDAP bind with the supplied username and password and checking whether it succeeds, rather than inferring success from a search that returned an entry.
- Use least-privilege bind accounts. The service account the application binds with should be able to read only the attributes it needs. When injection still occurs, a tightly scoped account limits what an attacker can enumerate.
- Validate input against an allowlist where the format is known. A username that must be alphanumeric can be checked against that pattern and rejected otherwise. Deny by default; do not try to enumerate bad characters.
- Suppress detailed directory errors. Verbose LDAP error text helps an attacker tune a blind extraction. Return a generic failure to the user and log the detail server-side.
If your team already parameterizes SQL by reflex, apply the identical reflex here. The grammar is different (prefix notation, parentheses, wildcards instead of quotes and semicolons) but the rule is the same: input never becomes syntax. Encode it, scope the account that runs it, and validate the shape you expect.
A worked example: from filter to bypass
Walking through a single request makes the mechanism concrete. Assume the application builds its login filter by string concatenation, something like (&(uid= followed by the raw username, then )(userPassword= followed by the raw password, then two closing parentheses. When a normal user named alice logs in with the password spring2026, the server receives a filter that reads: find the entry whose uid is alice and whose userPassword is spring2026. The directory returns that one entry, the bind or the match succeeds, and Alice is in.
Now change one field. The attacker leaves the password blank or arbitrary and submits a username of alice)(&)) or the more general *)(uid=*))(|(uid=*. Trace what happens to the string. The application still wraps the input in its fixed template, but the input now carries its own parentheses and its own boolean operators. The closing parenthesis the attacker supplied terminates the uid term early. The operators that follow open a fresh clause the developer never intended. By the time the parser reads the whole string, the password condition has been pushed outside the active logic or neutralized by an always-true wildcard clause, and the filter matches at least one entry regardless of the password.
The lesson from tracing it by hand is that the attacker is not guessing a password. The attacker is editing the query. Every character the application fails to escape is a character the attacker can promote from data into grammar. The parser has no way to know that the parenthesis came from a hostile form field rather than from the developer's template, because by the time it sees the string, both look identical.
The root of every injection class is the same: a parser receives a single flat string and cannot know which parts were meant as structure and which arrived as user data. Escaping restores that distinction by neutralizing the characters that carry meaning, so a parenthesis typed by a user stays a literal parenthesis and never becomes a grouping operator.
Detection signals
LDAP injection leaves traces on both the offensive and the defensive side. Knowing what to look for turns a vague worry into a concrete test.
- Behavioral response to metacharacters. Submit a single
)or a*in a field that reaches the directory and watch how the application responds. A server error, a stack trace mentioning the LDAP provider, or a sudden change in the number of results returned all suggest the input is reaching the filter unescaped. - Result-count swings. A search box that returns one record for a specific name but returns the entire directory for an input of
*is answering a wildcard it should have treated as a literal. That swing is the signature of an unescaped filter. - Login success without a valid password. If supplying a crafted username together with a wrong password ever produces an authenticated session, the code is inferring success from a search result rather than from a bind.
- Timing differences in blind cases. When no output is visible, a filter that matches versus one that does not can still differ in response time or in a subtle status change. A methodical attacker automates that difference to read data one character at a time.
- Directory server logs. On the server side, LDAP access logs that record raw filters will show malformed or unusually broad filters, multiple wildcard terms, or repeated near-identical queries walking through an alphabet. Those patterns are the fingerprint of an automated blind extraction in progress.
Logging the constructed filter at the directory tier, then alerting on filters that contain unexpected wildcards or an anomalous number of boolean operators, gives defenders a signal that no amount of front-end validation can produce on its own.
LDAP injection versus SQL injection
The two share a family resemblance, which helps developers who know one learn the other quickly. The differences are in the grammar and in the payoff.
| Aspect | SQL injection | LDAP injection |
|---|---|---|
| Target parser | Relational database query engine | Directory search filter parser |
| Structural characters | Quotes, semicolons, comment markers | Parentheses, asterisk, backslash, NUL, boolean operators |
| Notation | Infix (WHERE a = b AND c = d) | Prefix ((&(a=b)(c=d))) |
| Primary defense | Parameterized queries and prepared statements | RFC 4515 filter escaping and RFC 4514 DN escaping |
| Classic impact | Data theft, data modification, sometimes command execution | Authentication bypass and directory enumeration |
| CWE reference | CWE-89 | CWE-90 |
The most important practical difference is that most SQL stacks offer true parameterization, where the query structure is compiled separately from the data. Many LDAP libraries do not offer an equivalent compiled-filter primitive, so escaping the input before it enters the filter string carries more of the defensive weight. That places extra importance on using the platform's dedicated filter encoder every single time.
Common mistakes and misconceptions
Several recurring errors keep LDAP injection alive in codebases that believe they are safe.
- Escaping for the filter but building the DN by concatenation. A distinguished name and a search filter use different escaping rules. Code that carefully escapes filter input, then pastes user data into a DN with filter escaping or none at all, reopens the hole through the DN path defined by RFC 4514.
- Blocklisting characters instead of encoding them. Stripping parentheses and asterisks rejects legitimate input and still misses the backslash, which is both a literal users type and the escape character of the grammar. Encode with the standard function rather than trying to enumerate bad characters.
- Trusting client-side validation. A pattern check in the browser stops an honest user from typing a stray asterisk. It stops no attacker, who bypasses the page and speaks to the endpoint directly. Validation that matters runs on the server.
- Treating a returned entry as a passed password. The single most damaging design mistake is inferring authentication from the existence of a search result. Authentication must come from a successful bind with the supplied credentials.
- Leaking verbose directory errors. Detailed error text tells a blind attacker whether each probe matched, turning a slow guess into a fast one. Return a generic failure and keep the detail in server-side logs.
Teams often assume that because the directory is internal and the login page is behind a corporate boundary, filter injection does not matter. Directory-backed logins are frequently reachable from partner portals, VPN endpoints, and self-service reset flows that face outward. Treat every filter built from input as reachable and escape it regardless of where you believe it sits.
How LDAP injection became a known class
Directory services predate most modern web frameworks, and the pattern of building a filter by pasting strings together dates from an era when the directory and the application often ran on the same trusted network. As directory-backed single sign-on spread and login forms moved onto the public web, the same concatenation habit followed the code outward, and the filter grammar became an attack surface.
The technique was formalized alongside the broader injection family as web security matured. OWASP catalogued it as a distinct attack, MITRE assigned it CWE-90, and testing guides gave it a repeatable methodology. The reason it persists is not that the defense is hard, because RFC 4515 escaping is a solved problem with library support on every major platform. It persists because LDAP is unfamiliar territory for many developers, the escaping rules differ from the SQL rules they already know, and directory-backed features often ship with less review than the flashier parts of an application.
Escaping in practice
The abstract advice to escape filter metacharacters becomes concrete once you look at what escaping actually does. RFC 4515 defines an escape form where each special byte is replaced by a backslash followed by its two-digit hexadecimal value. An asterisk becomes \2a, an opening parenthesis becomes \28, a closing parenthesis becomes \29, and a backslash becomes \5c. The NUL byte becomes \00. After this transformation, an input of *)(uid=* no longer contains any character the parser reads as structure. The parser sees a literal string of escaped bytes and matches it, or fails to match it, as ordinary data. The attacker's parentheses and wildcards have been demoted back to text.
Two properties make this reliable when it is done correctly. The escaping is applied to each piece of user input individually, right before it is placed into the filter, so nothing downstream can reintroduce an unescaped character. And the backslash itself is escaped first, so an attacker cannot smuggle in a partial escape sequence that the parser later completes into a live metacharacter. Getting the order wrong, escaping parentheses but forgetting the backslash, is one of the subtle ways a hand-rolled encoder fails while appearing to work in casual testing.
The same logic extends to distinguished names, with a different character set. RFC 4514 governs DN escaping, where characters like the comma, plus sign, quotation mark, and leading or trailing spaces carry structural meaning. Code that constructs a DN from user input, for example to look up an entry by its full path, must use the DN escaping rules and not the filter rules. Mixing them is a frequent and quiet source of residual injection, because the filter path looks defended while the DN path stays open.
Frequently asked questions
Is LDAP injection still relevant if we use single sign-on? Yes. Many single sign-on and directory-backed login flows build search filters from the identifier a user supplies. Federation protocols on top do not remove the underlying filter if the identity provider still constructs one from input. Escape the filter regardless of the protocol wrapped around it.
Does using HTTPS or a web application firewall stop LDAP injection? No. HTTPS protects data in transit and has no effect on how the server assembles a filter. A firewall may catch some obvious payloads, and it is a supplement rather than a fix. The reliable defense is correct escaping in the code that builds the filter.
Can LDAP injection lead to more than a login bypass? It can. Beyond bypassing authentication, an attacker can enumerate usernames, group memberships, email addresses, and any attribute the bind account may read. Blind techniques can reconstruct attribute values character by character even when no output is displayed.
Why can I not simply parameterize the filter like a SQL query? Most LDAP libraries do not offer a compiled-filter primitive equivalent to a prepared SQL statement. The practical substitute is to escape each piece of user input with the library's RFC 4515 encoder before it enters the filter string, and to escape DN components separately with the RFC 4514 rules.
Is the salt-style trick of a random value any help here? No. That belongs to password storage, not to query construction. LDAP injection is a query-grammar problem, and the fix is neutralizing filter metacharacters, scoping the bind account, and verifying credentials with a bind.
How do I test an application for LDAP injection safely? Against systems you are authorized to test, submit filter metacharacters such as a lone parenthesis or an asterisk into fields that reach the directory and observe result counts, errors, and login behavior. The OWASP Testing Guide provides a structured methodology for directory-backed inputs.
What is the single highest-value fix if I can only do one thing? Route every piece of user input through the platform's dedicated LDAP filter-escaping function before it touches a filter string, and verify logins with a bind rather than a search result. Those two changes remove the most common and most damaging paths at once.
LDAP injection carries the same weight as any authentication flaw because it can end in a login bypass against a directory that guards the whole organization. Attackers catalog which login and search endpoints run on directories, and a filter built by concatenation is a reliable target. If you want to see how these directory and injection techniques show up in real exploitation data, our Exploit Intelligence dashboard tracks the vulnerabilities and access patterns attackers are actually using. Escape your filters, bind to verify credentials, and scope the account behind the query, and the endpoint stops being an easy win.
Related guides
Sources & further reading
Related guides
- Clickjacking Explained: UI Redress Attacks & Frame Defenses
How clickjacking hijacks a user's clicks with transparent iframes, what an attacker can trigger, and how to block framing with headers and CSP.
- Command Injection: Shell Metacharacters & Safe APIs
How OS command injection breaks out of an intended command via shell metacharacters, and why argument-array APIs beat string concatenation.
- CSRF Explained: SameSite Cookies & Anti-CSRF Tokens
How cross-site request forgery rides a logged-in user's cookies to force state-changing requests, and how SameSite, tokens, and double-submit stop it.