đŸ›Ąïž Application Security CheatSheet

LDAP Injection Deep Dive

LDAP Injection happens when untrusted input is treated as part of an LDAP query/filter instead of just data. The result is that the directory lookup can return the wrong user/group, bypass checks, or expose information.

Production reality: these issues usually slip through because everything looks “fine” in happy-path testing — until one weird request hits production.

Reference: Injection (overview)

Key idea: It’s not “about special characters”. It’s about filter structure changing based on untrusted input.

Deep reason it exists (how LDAP filtering works)

LDAP directories are queried using filters that behave like a small expression language. The server parses that filter into a logical tree (AND/OR/NOT plus attribute comparisons) and then evaluates it against directory entries.

Practical root cause: devs build filters like strings because it feels similar to SQL WHERE clauses, but forget the same rule: never let untrusted input become part of the query language.

First-principles mental model

Think of LDAP injection as: input crosses a trust boundary into a filter language.

experienced framing: “I look for any place user input influences filter logic or search scope, then I prove it safely by showing mismatched identity or group decisions.”

Vulnerable vs secure code patterns (Node.js)

Vulnerable pattern (minimal)

// Node/Express (using ldapjs)
// ❌ Vulnerable: filter built by concatenation
app.post("/login", async (req, res) => {
  const { username, password } = req.body;

  const client = ldap.createClient({ url: process.env.LDAP_URL });
  const baseDN = "ou=users,dc=example,dc=com";

  // Filter decides WHICH entry we authenticate
  const filter = "(uid=" + username + ")"; // ❌ user input becomes filter structure

  client.search(baseDN, { scope: "sub", filter }, (err, search) => {
    if (err) return res.status(500).json({ error: "Directory error" });

    let userDN = null;
    search.on("searchEntry", (entry) => { userDN = entry.dn.toString(); });
    search.on("end", () => {
      if (!userDN) return res.status(401).json({ error: "Invalid credentials" });

      // Bind as the found DN
      client.bind(userDN, password, (bindErr) => {
        if (bindErr) return res.status(401).json({ error: "Invalid credentials" });
        return res.json({ ok: true });
      });
    });
  });
});
This bug is dangerous because the filter controls who you bind as. If the filter can be manipulated, authentication can be redirected to an unintended entry or a broader match.

Secure pattern (defensive)

// ✅ Defensive pattern: escape untrusted input + constrain search + validate result
const { escapeFilter } = require("ldapjs");

app.post("/login", async (req, res) => {
  const { username, password } = req.body;

  // 1) Validate shape early (defense-in-depth)
  if (typeof username !== "string" || username.length < 1 || username.length > 64) {
    return res.status(400).json({ error: "Invalid input" });
  }

  const client = ldap.createClient({ url: process.env.LDAP_URL });

  // 2) Constrain the base DN to the correct subtree (reduce blast radius)
  const baseDN = "ou=users,dc=example,dc=com";

  // 3) Escape so input cannot change filter structure
  const safeUser = escapeFilter(username);
  const filter = `(&(objectClass=person)(uid=${safeUser}))`;

  // 4) Constrain attributes returned and enforce an exact match policy
  const opts = { scope: "sub", filter, sizeLimit: 2, timeLimit: 5, attributes: ["dn", "uid"] };

  client.search(baseDN, opts, (err, search) => {
    if (err) return res.status(500).json({ error: "Directory error" });

    const dns = [];
    search.on("searchEntry", (entry) => { dns.push(entry.dn.toString()); });

    search.on("end", () => {
      // If we didn't get EXACTLY one entry, fail closed.
      if (dns.length !== 1) return res.status(401).json({ error: "Invalid credentials" });

      client.bind(dns[0], password, (bindErr) => {
        if (bindErr) return res.status(401).json({ error: "Invalid credentials" });
        return res.json({ ok: true });
      });
    });
  });
});
Why this holds: escaping prevents filter-logic manipulation, baseDN scoping reduces search surface, and “exactly one match” avoids ambiguous multi-match cases.

Where LDAP injection still happens in modern stacks

Variants (why they differ)

1) Authentication redirection / “wrong DN” selection

The application uses a search filter to find a user DN, then binds as that DN. If the filter logic can be altered, the search may return an unintended entry and the bind step targets the wrong identity.

2) Authorization / group membership manipulation (logical)

Apps often decide permissions by searching groups or attributes (memberOf, groups, roles). If untrusted input affects the group filter, the authorization decision can be made on the wrong set of entries.

3) Information disclosure via over-broad search

Even without auth bypass, filter manipulation can cause overly broad matches, exposing user attributes (emails, phone numbers, org structure) through search endpoints.

4) Second-order LDAP injection

Input is stored (e.g., “displayName”, “department”) then later used in an LDAP filter in a different feature. This often appears in admin tooling and export/reporting flows.

Detection workflow (experienced-style, systematic)

This approach avoids “guessing tricks” and instead proves filter-logic sensitivity safely.

Step A — Identify LDAP-backed surfaces

Step B — Map trust boundaries

Step C — Look for “structure sensitivity” signals

Step D — Raise confidence safely

Safe validation workflow (defensive verification)

Goal: prove that untrusted input can influence directory selection or authorization decisions with minimal exposure.
  1. Baseline: run the request with a normal, valid input and record status, key response markers, and match expectations.
  2. Ambiguity test: check whether the system ever returns multiple matches or inconsistent selection when it should be unique.
  3. Decision proof: focus on outcomes: did the app authenticate the wrong identity, or assign the wrong role/group?
  4. Scope proof: verify the search is constrained to the intended subtree (base DN / OU) and cannot leak cross-tenant data.
  5. Regression test idea: add an automated test asserting “exactly one match” is required and filter inputs are escaped.
Interview-safe line: “I validate LDAP injection by proving wrong entry selection or wrong authorization decision, without extracting directory data.”

Exploitation progression (attacker mindset)

This explains how real attackers think, without giving copy/paste instructions. They escalate from “can I influence selection?” to “can I influence decisions?” to “what high-impact action can I unlock?”

Phase 1: Find filter-controlled decisions

Phase 2: Prove structure control (not just value control)

Phase 3: Convert structure control into decision control

Phase 4: Target high-impact outcomes

Interview takeaway: LDAP injection is about controlling directory selection and decisions—attackers go from “match control” → “identity/role control” → “business impact.”

Tricky edge cases & bypass patterns (what attackers look for)

Attacker hint: LDAP injection fixes fail most often because teams escape input but still allow ambiguous multi-match selection or broad search scope.

Confidence levels (how sure are you?)

Fixes that actually hold in production

1) Escape filter values correctly

2) Constrain search scope (reduce blast radius)

3) Enforce “exactly one match” for identity selection

4) Separate auth from directory lookup where possible

Defense-in-depth: input validation, structured logging, rate limiting on auth endpoints, and alerting on suspicious match anomalies.

Regression prevention (how to prevent regressions)

Interview-ready answers (60-second + 2-minute)

60-second answer

LDAP injection is when user input changes an LDAP filter’s logic instead of being treated as a value. It usually appears in “search then bind” login flows, group/role checks, and directory search endpoints. I prevent it by escaping filter values using library helpers, constraining baseDN/scope, enforcing “exactly one match” for identity selection, and adding tests so ambiguous multi-matches fail closed. I validate it safely by proving wrong identity/role decisions, not by dumping directory data.

2-minute answer

I explain LDAP injection as a trust-boundary failure: untrusted input crosses into a filter language and can alter the directory query tree. In practice, the highest risk is when the filter selects a DN and the app then binds as that DN, or when group membership checks drive authorization. My fix is production-grade: escape values with escapeFilter, scope searches to the correct OU/baseDN, add size/time limits, and enforce an “exactly one match” invariant. I also prevent regressions by centralizing directory access, adding unit tests for escaping and multi-match behavior, and monitoring for anomalies like unexpected match counts. During testing, I focus on proving decision impact (wrong user selected / wrong role decision) with minimal exposure.

Checklist (quick review)

Remediation playbook

  1. Contain: add server-side escaping immediately and fail closed on multi-match; narrow baseDN if possible.
  2. Fix: refactor LDAP access into a hardened module that only exposes safe query builders.
  3. Scope: search the codebase for any LDAP filter concatenation and “search then bind” flows.
  4. Harden: add size/time limits, attribute allow-lists, and strict uniqueness checks.
  5. Test: add unit + integration tests for escaping and multi-match failure behavior.
  6. Monitor: alert on unexpected match counts, directory errors, and unusual query rates.

Interview Questions & Answers (Easy → Hard)

How to use: Answer in plain English first (10–15 seconds), then go deeper (1–2 minutes) with root cause, edge cases, and trade-offs. Keep it interview-safe.

Easy

  1. What is LDAP Injection?
    A: Plain: It’s when user input changes an LDAP search query instead of being treated as data. Deep: LDAP filters are parsed into a logical tree; concatenation lets untrusted input influence that tree and the entries returned.
  2. Where do you typically see it?
    A: Plain: Login, user search, and group checks. Deep: “search then bind” auth flows, role mapping from group membership, admin directory search endpoints, and account recovery lookups.
  3. What is the primary defense?
    A: Plain: Escape user input before it goes into the LDAP filter. Deep: Use library helpers (e.g., escapeFilter) and keep the filter template fixed so the input is only a value.
  4. Is input validation alone sufficient?
    A: Plain: No. Deep: Validation reduces risk, but the robust control is correct escaping plus strict invariants (exactly one match) and constrained search scope.
  5. Why is “exactly one match” important in auth flows?
    A: Plain: Because logging in should target one user only. Deep: Multi-match ambiguity creates “first entry wins” behavior that can be abused or cause wrong identity selection.
  6. What’s the difference between LDAP injection and SQL injection?
    A: Plain: Both are “input becomes query logic”, but in different languages. Deep: SQL injection targets SQL parsing; LDAP injection targets LDAP filter parsing and directory selection decisions.
  7. What’s a safe thing to log during investigation?
    A: Plain: Outcomes and counts, not directory data. Deep: Log match count, endpoint, and anonymized identifiers; avoid logging returned attributes or full DNs if that’s sensitive.

Medium

  1. Scenario: a login flow does “search then bind”. Where is the risk?
    A: Plain: The search might find the wrong user. Deep: If filter logic is influenced, the selected DN can be wrong; then the bind step authenticates against an unintended identity path. Fix with escaping, scoped baseDN, and exact-one match enforcement.
  2. Scenario: user search endpoint returns profiles. What’s the LDAP injection impact?
    A: Plain: It might return too many users. Deep: Filter manipulation can broaden results and leak attributes. Fix by escaping, applying strict server-side constraints (objectClass + allowed attributes), and rate limiting.
  3. How do you validate LDAP injection without being destructive?
    A: Plain: I prove decision changes, not data extraction. Deep: Compare baseline vs modified inputs for deterministic changes in match counts, selected identity, or role decision, while minimizing returned attributes and avoiding broad dumps.
  4. Follow-up: what tells you the issue is “structure control”?
    A: Plain: When small input shape changes cause big result/decision changes. Deep: Repeatable multi-match selection, unexpected “valid” outcomes, or directory parsing errors indicate filter-tree influence rather than value mismatches.
  5. How do you reduce blast radius besides escaping?
    A: Plain: Limit where and how you search. Deep: Narrow baseDN/OU, set size/time limits, request only necessary attributes, and ensure directory permissions are least-privileged for the app account.
  6. Scenario: group membership drives authorization. What’s the pitfall?
    A: Plain: Wrong group check means wrong permissions. Deep: If the group filter is influenced or the group identifier is user-controlled, authorization can be computed on the wrong set. Fix with fixed templates, allow-list group names/IDs, and centralized policy checks.
  7. Follow-up: what’s the best long-term design choice?
    A: Plain: Prefer SSO over direct LDAP auth. Deep: Use OIDC/SAML where possible; if LDAP is required, keep directory access behind a hardened service/module with strict safe builders and tests.

Hard

  1. Scenario: escaping is applied, but the app still has LDAP injection-like behavior. How?
    A: Plain: The bug might be elsewhere, like scope or multi-match. Deep: Escaping blocks filter manipulation, but broad baseDN/scope plus non-unique attributes can still cause wrong selection. Multi-match “first wins” is a separate logic flaw that looks similar in impact.
  2. Scenario: you see multi-match results for a “unique” username. What do you investigate?
    A: Plain: Directory uniqueness and normalization. Deep: Check attribute uniqueness policy, case/Unicode normalization, alias attributes, and whether the app searches across multiple OUs/tenants. Fix by enforcing uniqueness at directory level and failing closed on multi-match.
  3. Scenario: the app uses a user-controlled attribute name to decide what to search on. Risk?
    A: Plain: It can turn into a different query than intended. Deep: Even if values are escaped, allowing user-controlled attribute selection can broaden scope or target sensitive attributes. Fix by allow-listing attribute names and keeping filter templates fixed.
  4. Follow-up: how would you prevent regressions at scale?
    A: Plain: Centralize the safe way and ban the unsafe way. Deep: Create a shared LDAP helper that requires escaped inputs, scoped baseDN, and exact-one match for identity selection; add lint/code review rules to block string concatenation in filters.
  5. Scenario: a password reset uses LDAP search by email. What’s the subtle risk?
    A: Plain: Wrong account selection. Deep: Email may not be unique or could match multiple entries; combined with filter issues, you get account recovery misbinding. Fix by uniqueness requirements, exact-one match, and additional verification steps.
  6. Follow-up: what evidence would make you say “high confidence”?
    A: Plain: Repeatable wrong identity/role decisions. Deep: Deterministic proof that the app authenticates/authorizes as the wrong principal or returns out-of-scope directory results, captured with minimal attributes and clear baselines.
  7. Scenario: the directory supports referrals and the client follows them. Any security implications?
    A: Plain: It can change where queries go. Deep: Referral following can expand the search surface or route queries to unexpected directory servers in some environments; treat it as a configuration risk and constrain it based on your environment and threat model.
  8. Follow-up: what are your trade-offs when hardening auth flows?
    A: Plain: Security vs usability/performance. Deep: Narrow scope and strict matching reduce risk but may break edge accounts; mitigate by cleaning directory data, enforcing uniqueness, caching safely, and implementing clear error handling that fails closed.
  9. Scenario: you can’t change the LDAP integration quickly. What short-term mitigations help?
    A: Plain: Reduce exposure and add guardrails. Deep: Tighten baseDN/scope, add size/time limits, reduce attributes returned, rate limit endpoints, and add monitoring for match anomalies—while planning a proper escaping + refactor fix.
Interview tip: When asked “how to test”, describe a defensive validation workflow (baseline → repeatable signal → minimal proof → remediation), not exploitation steps.