LDAP Injection in Vibe Coded Apps
How to find and fix enterprise authentication vulnerabilities in Active Directory integrations
LDAP injection exploits unescaped user input in directory service queries like Active Directory. Attackers send payloads like admin)(& to bypass authentication entirely.
Ranked under OWASP A03:2021 Injection.
Source: OWASP Top 10 (2021) | CWE-90 | Verizon 2025 DBIR
What is LDAP injection?
LDAP injection is a vulnerability where attackers manipulate Lightweight Directory Access Protocol queries by inserting special characters into user input fields. It targets enterprise directory services like Active Directory, Azure AD (now Entra ID), and OpenLDAP.
Think of it like SQL injection, but for enterprise authentication systems.
Instead of attacking a database with ' OR '1'='1, attackers use LDAP filter syntax with characters like *, (, ), &, and |.
According to the Verizon 2025 Data Breach Investigations Report, 88% of breaches involve compromised credentials. LDAP injection is one way attackers bypass credential verification entirely. CWE-90 classifies this as "Improper Neutralization of Special Elements used in an LDAP Query."
Why does LDAP injection matter for enterprise apps?
LDAP powers authentication for millions of enterprise applications through Active Directory, SSO systems, and employee directories. If you're vibe coding enterprise SaaS with SSO or building internal tools that authenticate against corporate directories, LDAP injection directly affects you.
Recent high-profile vulnerabilities demonstrate the severity:
- CVE-2024-49113 (CVSS 9.8): Windows LDAP remote code execution vulnerability known as "LDAPNightmare"
- CVE-2025-54918: NTLM LDAP authentication bypass enabling SYSTEM-level escalation
- CVE-2024-37782: Gladinet CentreStack LDAP injection through username field
How does LDAP injection work?
LDAP injection works by inserting special characters that modify the structure of LDAP filter queries. The most common attack bypasses authentication by truncating the filter logic.
Authentication Bypass
The classic LDAP injection attack uses admin)(& as a username:
// Vulnerable LDAP filter with user input
const filter = `(&(uid=${username})(userPassword=${password}))`
// Normal input:
// username = "admin", password = "secret123"
// Filter: (&(uid=admin)(userPassword=secret123))
// Attack input:
// username = "admin)(&", password = "anything"
// Filter: (&(uid=admin)(&)(userPassword=anything))
// ^ Always true! Password check ignoredThe closing parenthesis ) terminates the uid check, and (& creates an always-true condition.
The password verification is effectively bypassed.
Special Character Exploitation
LDAP has several dangerous characters that attackers exploit:
Wildcard Enumeration
Attackers use wildcards to enumerate users or bypass exact matching:
// Attack payload: username = "*"
// Filter: (&(uid=*)(userPassword=anything))
// Result: Matches ANY user in the directory
// Attack payload: username = "a*"
// Filter: (&(uid=a*)(userPassword=test))
// Result: Enumerate all users starting with 'a'Why do AI tools generate vulnerable LDAP code?
AI tools generate insecure LDAP authentication because their training data includes enterprise examples without proper escaping. When you ask Cursor or Claude Code to build Active Directory authentication, they generate string concatenation patterns.
Common AI-generated vulnerable patterns
// What Cursor typically generates (VULNERABLE)
async function authenticate(username, password) {
const filter = `(&(uid=${username})(userPassword=${password}))`
const result = await ldapClient.search('ou=users,dc=example,dc=com', {
filter: filter
})
return result.searchEntries.length > 0
}
// What Bolt generates for quick auth endpoints (VULNERABLE)
app.post('/api/login', async (req, res) => {
const { username, password } = req.body
const filter = `(&(cn=${username})(userPassword=${password}))`
const result = await ldap.search('dc=example,dc=com', { filter })
res.json({ authenticated: result.length > 0 })
})Why this happens:
- Training data bias: Enterprise auth examples in training data rarely show LDAP-specific escaping
- LDAP obscurity: Less documentation about LDAP injection than SQL injection
- String concatenation is simpler: AI generates the most straightforward working code
- No security context: AI doesn't know your code handles untrusted user input
What could happen if I have LDAP injection?
LDAP injection against enterprise directories can compromise your entire organization's authentication system.
- Authentication bypass: Log in as any user without knowing their password using
admin)(& - Privilege escalation: Access admin accounts or modify group memberships
- Data exfiltration: Extract employee records, emails, and organizational data from the directory
- Lateral movement: Use compromised credentials to access other enterprise systems
- Directory enumeration: Map all users, groups, and organizational structure
How do I detect LDAP injection?
Detect LDAP injection by searching for code that builds LDAP filters using string concatenation or template literals with user input.
// String interpolation in LDAP filters (DANGEROUS)
`(&(uid=${username})`
`(cn=${user})`
filter = f"(&(uid={username})"
// Template literals with req.body (DANGEROUS)
filter: `(&(uid=${req.body.username})`
// No escaping before filter construction (DANGEROUS)
ldapClient.search(baseDN, { filter: userInput })
// Regex patterns to find these:
// \(\s*(&|\|)\s*\([^)]*\$\{
// ldap.*search.*\$\{
// filter.*=.*[fF]".*\{Quick manual test: Enter admin)(& as a username in your login form. If authentication succeeds with any password, you have LDAP injection.
Don't want to test manually?
Scan your enterprise auth codeHow do I fix LDAP injection?
Fix LDAP injection by escaping special characters before including user input in LDAP filters. Use library-provided methods instead of building filter strings manually.
AI Fix Prompt
Copy this prompt into Cursor, Claude Code, or Bolt to automatically fix LDAP injection in your codebase:
Manual Fix - Node.js (ldapjs)
// String interpolation - DANGEROUS
async function authenticate(username, password) {
const filter = `(&(uid=${username})(userPassword=${password}))`
const result = await ldapClient.search(
'ou=users,dc=example,dc=com',
{ filter: filter }
)
return result.searchEntries.length > 0
}
// Attack: username="admin)(&" bypasses auth// Use Filter objects - SAFE
import { EqualityFilter, AndFilter } from 'ldapjs'
async function authenticate(username, password) {
// Validate input format first
if (!/^[a-zA-Z0-9._-]+$/.test(username)) {
throw new Error('Invalid username format')
}
const filter = new AndFilter({
filters: [
new EqualityFilter({ attribute: 'uid', value: username }),
new EqualityFilter({ attribute: 'userPassword', value: password })
]
})
const result = await ldapClient.search(
'ou=users,dc=example,dc=com',
{ filter: filter.toString() }
)
return result.searchEntries.length > 0
}Manual Fix - Python (ldap3)
# f-string interpolation - DANGEROUS
def authenticate(username, password):
filter = f"(&(uid={username})(userPassword={password}))"
conn.search(base_dn, filter)
return len(conn.entries) > 0
# Attack: username="admin)(&" bypasses auth# Use escape_filter_chars - SAFE
from ldap3.utils.conv import escape_filter_chars
import re
def authenticate(username, password):
# Validate input format first
if not re.match(r'^[a-zA-Z0-9._-]+$', username):
raise ValueError('Invalid username format')
safe_user = escape_filter_chars(username)
safe_pass = escape_filter_chars(password)
filter = f"(&(uid={safe_user})(userPassword={safe_pass}))"
conn.search(base_dn, filter)
return len(conn.entries) > 0Prevention methods by priority
Use Filter Objects
Use library-provided Filter objects instead of string concatenation. They handle escaping automatically.
new EqualityFilter({ attribute: 'uid', value: username })Escape Special Characters
If you must build filter strings, escape all LDAP special characters: * ( ) & | ! \\ NUL
escape_filter_chars(username)Validate Input Format
Allowlist valid characters for usernames before any LDAP operation.
/^[a-zA-Z0-9._-]+$/.test(username)Least Privilege Binding
Use read-only LDAP binding accounts with minimal permissions.
// Bind with service account, not adminHow does LDAP injection compare to other injection attacks?
LDAP injection follows the same principle as SQL and NoSQL injection - exploiting unvalidated user input in queries. The main difference is the query language and target system.
| Aspect | SQL Injection | NoSQL Injection | LDAP Injection |
|---|---|---|---|
| Target | Relational databases | Document stores (MongoDB) | Directory services (Active Directory) |
| Query Language | SQL syntax | JSON/operators | LDAP filter syntax |
| Attack Characters | ' " ; -- | {"$ne": ""} | * ( ) & | ! \ |
| Primary Impact | Data breach | Auth bypass | Enterprise auth bypass |
| Prevention | Parameterized queries | Type validation | Filter escaping |
Frequently asked questions
What is LDAP injection?
LDAP injection is a vulnerability where attackers manipulate LDAP queries by inserting special characters into user input fields. It targets enterprise directory services like Active Directory, Azure AD, and OpenLDAP. Classified as CWE-90, it allows authentication bypass, privilege escalation, and data exfiltration from corporate directories. The attack exploits unescaped user input in LDAP filter strings.
How does LDAP injection work?
LDAP injection works by inserting special characters like *, (, ), &, and | into LDAP filter queries. For authentication bypass, an attacker enters "admin)(&" as a username. This transforms the filter from (&(uid=admin)(&)(userPassword=secret)) into a query that always returns true, bypassing password verification. The closing parenthesis truncates the original query.
What is the difference between SQL injection and LDAP injection?
SQL injection targets relational databases using SQL syntax like single quotes and semicolons. LDAP injection targets directory services using LDAP filter syntax with characters like *, (, ), and &. SQL injection steals database records. LDAP injection bypasses enterprise authentication and accesses directory data. Both exploit unvalidated user input, but the query language and target systems differ.
How do I prevent LDAP injection?
Prevent LDAP injection by escaping special characters before including user input in LDAP filters. In Node.js, use ldapjs parseFilter with EqualityFilter objects instead of string concatenation. In Python, use ldap3 escape_filter_chars(). Also validate input against an allowlist, limit input length, and use least-privilege LDAP binding accounts with read-only access where possible.
Is Active Directory vulnerable to LDAP injection?
Yes. Active Directory uses the LDAP protocol for queries and is vulnerable when applications build LDAP filters with unescaped user input. Recent CVEs include CVE-2024-49113 (CVSS 9.8, Windows LDAP RCE) and CVE-2025-54918 (NTLM LDAP authentication bypass). Enterprise apps integrating with AD for SSO must validate and escape all user-provided values.
What characters are used in LDAP injection?
The dangerous LDAP special characters are: * (wildcard matching any value), ( and ) (filter grouping), & (AND operator), | (OR operator), ! (NOT operator), \ (escape character), and NUL (null byte). Attackers use these to modify filter logic, bypass authentication, or enumerate directory entries. All must be escaped in user input.
Can LDAP injection bypass authentication?
Yes. LDAP injection commonly bypasses authentication. The classic attack uses "admin)(&" as a username with any password. This modifies the filter to (&(uid=admin)(&)(userPassword=anything)), which evaluates to true because the password check is truncated. The attacker gains access as the admin user without knowing the actual password.
Related content
Scan your enterprise auth code for LDAP injection
Check your Active Directory integration and LDAP authentication for injection vulnerabilities.
Try Vibeship Scanner