Safeguard
Security

LDAP Injection Explained With a Real Example

A walkthrough of a concrete LDAP injection example, why the filter syntax makes it dangerous, and how to detect and remediate it in real code.

Priya Mehta
Security Analyst
6 min read

The clearest LDAP injection example is a login form that builds a directory search filter by string-concatenating a username, letting an attacker inject filter operators that turn a specific query into a wildcard match. LDAP injection is the directory-service cousin of SQL injection: instead of poisoning a SQL statement, the attacker poisons the search filter or distinguished name sent to an LDAP server such as Active Directory or OpenLDAP.

The reason it stays common is that LDAP filter syntax is unfamiliar to most developers. People who instinctively reach for parameterized SQL queries will happily paste user input straight into a filter string because they have never seen the syntax abused.

How LDAP filters work

LDAP search filters use a prefix (Polish) notation wrapped in parentheses. A filter to find a user by their uid looks like this:

(uid=jsmith)

You combine conditions with boolean operators. An AND of two conditions looks like:

(&(uid=jsmith)(objectClass=person))

The special characters that matter for injection are (, ), *, \, and the boolean prefixes &, |, and !. The asterisk is a wildcard that matches any value. If any of those characters reach the filter unescaped, the attacker controls the query logic.

A concrete LDAP injection example

Consider an authentication routine that checks whether a username and password match a directory entry. A naive implementation builds the filter by concatenation:

String filter = "(&(uid=" + username + ")(userPassword=" + password + "))";
NamingEnumeration<SearchResult> results =
    ctx.search("ou=people,dc=example,dc=com", filter, controls);
boolean authenticated = results.hasMore();

The developer expects username=jsmith and password=hunter2, producing:

(&(uid=jsmith)(userPassword=hunter2))

Now suppose the attacker submits the username value *)(uid=*))(|(uid=* and leaves the password field with anything. The filter becomes:

(&(uid=*)(uid=*))(|(uid=*)(userPassword=anything))

The password condition has been detached from the AND clause. The search now matches essentially every entry that has a uid, and hasMore() returns true. Authentication succeeds without a valid password. A simpler variant is submitting * as the username to match the first account the directory returns.

This is an authentication bypass, but the same flaw enables information disclosure. If a "search users" feature builds a filter from a query parameter, injecting *)(mail=* can widen a narrow lookup into a full directory dump, leaking email addresses, group memberships, or other attributes the application never meant to expose.

Blind LDAP injection

Not every injectable endpoint returns the matched entries directly. In blind LDAP injection, the application only reveals whether a filter matched anything — a "user found" versus "user not found" response, or a timing difference. An attacker can still extract data one character at a time by testing filters like (&(uid=admin)(userPassword=a*)), then (...b*), and so on, watching which prefix produces a match. It is slow but fully automatable, the same way blind SQL injection works.

How to detect LDAP injection

Static analysis is the most reliable first pass. Look for any code path where request data flows into a search filter or a distinguished name without passing through an escaping routine. The taint sources are request parameters, headers, and cookies; the sinks are the search(), bind(), and modify calls of your LDAP client library. A static application security testing tool traces these source-to-sink flows automatically; you can also grep for filter concatenation as a quick manual sweep:

grep -rnE '"\(&?\(.*\+.*"' src/

Dynamic testing complements this. Send filter metacharacters — a lone *, an unbalanced ), a )(objectClass=* fragment — into every field that might reach a directory query, and watch for changed result counts, authentication anomalies, or server errors. Automated scanners such as those described in our DAST product overview probe these injection points during a crawl. Directory servers also log malformed filters, so a spike in filter parse errors in your LDAP logs is a strong signal someone is probing.

Remediation that actually works

Escape every piece of untrusted input before it reaches a filter. The rules come from RFC 4515 for search filters and RFC 4514 for distinguished names, and every mature LDAP library ships an implementation so you never hand-roll it.

In Java, use the utilities from your LDAP SDK rather than concatenation. The UnboundID LDAP SDK, for example, exposes an encoder:

import com.unboundid.util.Filter;

Filter filter = Filter.createANDFilter(
    Filter.createEqualityFilter("uid", username),
    Filter.createEqualityFilter("userPassword", password));

Because username is passed as a value to createEqualityFilter, the SDK escapes the metacharacters, so *)(uid=* becomes the literal string \2a\29\28uid=\2a inside the filter and matches nothing.

In Python with ldap3, use the escape_filter_chars helper:

from ldap3.utils.conv import escape_filter_chars

safe = escape_filter_chars(username)
conn.search('ou=people,dc=example,dc=com',
            f'(uid={safe})',
            attributes=['cn', 'mail'])

Beyond escaping, apply defense in depth. Bind to the directory with a low-privilege service account so a successful injection cannot read sensitive attributes or write changes. Never bind as the input-supplied DN for authentication without a preceding, escaped search. Validate input against an allowlist where the format is known — a uid is rarely going to contain parentheses or asterisks legitimately, so reject those characters outright at the edge. And keep authentication logic out of raw filters entirely by using a proper bind operation to verify credentials rather than searching for a password attribute.

FAQ

Is LDAP injection the same as SQL injection?

They share a root cause — untrusted input concatenated into a query language — but the syntax differs. SQL injection abuses SQL statements; LDAP injection abuses directory search filters and distinguished names. The metacharacters, the escaping rules, and the impact (authentication bypass and attribute disclosure rather than table dumps) are specific to LDAP.

Does using Active Directory instead of OpenLDAP prevent it?

No. LDAP injection is a flaw in how your application builds filters, not in the directory server. Active Directory, OpenLDAP, and every other LDAP-compatible directory interpret the same filter syntax, so an unescaped filter is exploitable regardless of the backend.

Can a web application firewall stop LDAP injection?

A WAF can block some obvious payloads containing *)( sequences, but it is a mitigation, not a fix. Attackers vary encoding and payload structure, and blind injection can stay under signature thresholds. Escaping input at the application layer is the actual remedy.

How do I test my own code for this quickly?

Submit a single asterisk and an unbalanced parenthesis into every field that could reach a directory lookup, then compare result counts and error responses. If a * widens results or an unbalanced ) produces a server-side parse error, the filter is being built by concatenation and needs escaping. Pair this with a static scan to catch paths you cannot reach manually.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.