High CWE-90 OWASP A03:2021

LDAP Injection in Vibe Coded Apps

How to find and fix enterprise authentication vulnerabilities in Active Directory integrations

Quick Answer

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.

#3
OWASP Ranking
88%
Credential Breaches
High
Severity

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 ignored

The 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 - matches any value
( ) Filter grouping - controls query structure
& AND operator - combines conditions
| OR operator - alternative conditions
! NOT operator - negates conditions
\ Escape character
NUL Null byte - terminates strings

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.

Patterns to search for
// 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 code

How 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:

Copy-paste this prompt
Fix all LDAP injection vulnerabilities in my codebase. ## What to look for Search for these vulnerable patterns in LDAP authentication code: 1. String concatenation in LDAP filters: - Template literals: `(&(uid=${username})(userPassword=${password}))` - String interpolation: f"(&(uid={username})" - Concatenation: "(&(uid=" + username + ")" 2. Missing input escaping: - User input passed directly to ldapClient.search() - No escaping of LDAP special characters: * ( ) & | ! \ NUL - req.body or request.json() values in filter strings 3. Dangerous filter construction: - Building filters with string operations instead of library methods - No use of parseFilter, EqualityFilter, or escape_filter_chars - Accepting wildcards (*) from user input ## How to fix ### Node.js (ldapjs) - Use Filter Objects: ```typescript // Before (vulnerable) const filter = `(&(uid=${username})(userPassword=${password}))` const result = await ldapClient.search(baseDN, { filter }) // After (secure) - Use EqualityFilter objects import { EqualityFilter, AndFilter } from 'ldapjs' const filter = new AndFilter({ filters: [ new EqualityFilter({ attribute: 'uid', value: username }), new EqualityFilter({ attribute: 'userPassword', value: password }) ] }) const result = await ldapClient.search(baseDN, { filter: filter.toString() }) ``` ### Node.js - Manual Escaping Function: ```typescript function escapeLdap(input: string): string { return input .replace(/\\/g, '\\5c') .replace(/\*/g, '\\2a') .replace(/\(/g, '\\28') .replace(/\)/g, '\\29') .replace(/\x00/g, '\\00') } const safeUsername = escapeLdap(username) const filter = `(&(uid=${safeUsername})(userPassword=${escapeLdap(password)}))` ``` ### Python (ldap3): ```python # Before (vulnerable) filter = f"(&(uid={username})(userPassword={password}))" conn.search(base_dn, filter) # After (secure) - Use escape_filter_chars from ldap3.utils.conv import escape_filter_chars 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) ``` ## Additional security measures 1. Add input validation with allowlist: ```typescript const usernameRegex = /^[a-zA-Z0-9._-]+$/ if (!usernameRegex.test(username)) { throw new Error('Invalid username format') } ``` 2. Use least-privilege LDAP binding account 3. Implement rate limiting on auth attempts 4. Log failed authentication attempts ## After fixing 1. Search for remaining patterns: \$\{.*\}.*ldap|ldap.*\$\{ 2. Test with payload: admin)(& as username 3. List all files modified with before/after snippets Please proceed systematically through my codebase.

Manual Fix - Node.js (ldapjs)

VULNERABLE
// 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
SECURE
// 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)

VULNERABLE
# 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
SECURE
# 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) > 0

Prevention methods by priority

1

Use Filter Objects

Use library-provided Filter objects instead of string concatenation. They handle escaping automatically.

new EqualityFilter({ attribute: 'uid', value: username })
2

Escape Special Characters

If you must build filter strings, escape all LDAP special characters: * ( ) & | ! \\ NUL

escape_filter_chars(username)
3

Validate Input Format

Allowlist valid characters for usernames before any LDAP operation.

/^[a-zA-Z0-9._-]+$/.test(username)
4

Least Privilege Binding

Use read-only LDAP binding accounts with minimal permissions.

// Bind with service account, not admin

How 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.

AspectSQL InjectionNoSQL InjectionLDAP Injection
TargetRelational databasesDocument stores (MongoDB)Directory services (Active Directory)
Query LanguageSQL syntaxJSON/operatorsLDAP filter syntax
Attack Characters' " ; --{"$ne": ""}* ( ) & | ! \
Primary ImpactData breachAuth bypassEnterprise auth bypass
PreventionParameterized queriesType validationFilter 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