CWE-90 is LDAP injection. The product builds all or part of an LDAP query from externally influenced input, without neutralizing, or while incorrectly neutralizing, special elements that could modify the intended query when it is sent to a downstream component.
LDAP directories sit behind many sign-in flows, address books and access checks. Any application that searches a directory using values a user typed is exposed, and the most sensitive place is usually authentication.
How it happens
The shape is the same as SQL injection, with a different syntax. A search filter is assembled by joining text, and parentheses, asterisks and other special characters in the user's value change the meaning of the filter.
# Vulnerable: user name goes straight into the filter
flt = "(&(uid=" + username + ")(userPassword=" + password + "))"
conn.search_s(base, ldap.SCOPE_SUBTREE, flt)
If username contains filter syntax, the attacker can close the condition early and make the filter match without knowing a password. Even a wildcard can turn a lookup for one user into a lookup for all of them.
The fix is to escape each value for the filter context before building the filter:
from ldap.filter import escape_filter_chars
flt = "(&(uid=%s)(userPassword=%s))" % (
escape_filter_chars(username),
escape_filter_chars(password),
)
Escaping the value turns its special characters into plain text. For stronger sign-in code, search for the user's entry by escaped uid, then verify the password by binding as that user instead of putting the password in a filter at all. That is design advice rather than a MITRE rule, but it removes a whole class of mistakes.
The observed examples in the record:
- CVE-2021-41232: a chain in a Go-based agile development product where the authentication routine does not escape the user name, which allows LDAP injection.
- CVE-2005-2301: a server that does not properly escape LDAP queries, which allows remote attackers to cause a denial of service and possibly conduct an LDAP injection attack.
See CVE-2021-41232 and CVE-2005-2301. The first is the kind to remember: the escape was missing in an authentication routine, which is the place where it matters most.
What an attacker gains
MITRE says an attacker could include input that changes the LDAP query, which allows unintended commands or code to be executed, allows sensitive data to be read or modified, or causes other impacts. The listed impact areas are confidentiality, integrity and availability, through reading and modifying application data and executing unauthorized code or commands. As the CVE above shows, a login bypass is a realistic outcome.
How to find it
MITRE lists automated static analysis (SAST) as a way to find some instances by analyzing source or compiled code without executing it, usually by building a model of data flow from inputs to the query. Combine that with a manual review of every call that searches a directory:
- List every place the code builds an LDAP filter or distinguished name.
- Mark which values come from outside, including user names, group names and search terms.
- Check that each one is escaped for the right context before it is joined in.
- Pay extra attention to authentication and authorization lookups.
Test by submitting filter characters such as parentheses and asterisks in each field and watching for changes in results or errors.
How to fix it
The MITRE record gives one mitigation, at implementation time: input validation. Assume all input is malicious. Use an accept known good strategy, with a list of acceptable inputs that strictly conform to the specification, and reject anything that does not, or transform it into something that does.
When validating, consider all relevant properties: length, type, the full range of acceptable values, missing or extra inputs, syntax, consistency across related fields and conformance to business rules. MITRE's example is that "boat" is syntactically valid because it is alphanumeric, but it is not valid if the field should only hold colors such as "red" or "blue".
MITRE also warns not to rely only on looking for malicious or malformed input. That is likely to miss at least one undesirable input, especially if the environment changes, and gives attackers room to bypass the check. A denylist is useful for detecting likely attacks or rejecting inputs that are clearly malformed.
Putting it together, in plain steps:
- Validate each value against what it should be: a user name pattern, a known group name list, a length limit.
- Escape each value for the LDAP filter or distinguished name context when building a query.
- Use a bind with the user's own credentials to check passwords instead of comparing in a filter.
- Give the application's directory account only the read access it needs.
Libraries matter too. A directory client or sign-in plugin may build filters unsafely in code you did not write. The free in-browser dependency scan at gold.safeguard.sh/scan needs no login, keeps your file on your machine and covers up to 100 dependencies. Reachability analysis shows whether your code calls the vulnerable function, so you can fix the findings that matter first.
Quick check
How is LDAP injection like SQL injection?
Both join user text into a query, and special characters in the value change what the query means. The fix is to keep data separate from syntax, by escaping or parameterizing.
Why is a missing escape in a login routine so serious?
Authentication decides who gets in. If the user name changes the filter, an attacker may match an entry without knowing the password. CVE-2021-41232 is an example in the record.
Is a denylist of bad characters enough?
No. MITRE says to prefer accepting known good input, and use denylists to detect likely attacks or reject clearly malformed input.