Affected versions

Symfony Security HTTP versions <5.4.56, >=6, <6.4.50, >=7, <7.4.23, >=8, <8.1.11 are affected by this security issue.

The issue has been fixed in Symfony Security HTTP 5.4.56, 6.4.50, 7.4.23, 8.1.11.

Description

X509Authenticator extracts the user identifier from the Subject DN (Distinguished Name) that the web server passes in $_SERVER['SSL_CLIENT_S_DN']. The fix for CVE-2026-45063 anchored that extraction to an RDN (Relative Distinguished Name) boundary:

1
'#(?:^|[,/])\s*(?:emailAddress|1\.2\.840\.113549\.1\.9\.1)=([^,/@]++@[^,/]++)#'

(?:^|[,/]) is not escape-aware. The expression runs against an already serialized DN, so it cannot tell an RDN separator from a separator character that occurs inside a value. Both serializations a web server can emit are affected, in opposite directions.

Under RFC 2253, used by Apache and by nginx's $ssl_client_s_dn, a comma inside a value is escaped as \,, and the expression reads it as a separator regardless. A certificate whose single CN is jdoe,emailAddress=admin@example.com, issued to a holder whose genuine address is jdoe@example.com, serializes to:

1
CN=jdoe\,emailAddress=admin@example.com,emailAddress=jdoe@example.com

RFC 2253 emits RDNs in reverse order, so the poisoned CN is scanned before the genuine emailAddress attribute and the authenticator returns admin@example.com.

RFC 2253 does not treat / as a separator, so it leaves it bare inside a value, while the expression treats every / as one. A certificate whose single CN is jdoe/emailAddress=admin@example.com therefore authenticates as admin@example.com as well, with no emailAddress attribute needed. The legacy X509_NAME_oneline() format, used by Apache's LegacyDNStringFormat and by nginx's $ssl_client_s_dn_legacy, is the mirror image: it escapes / as \/ and leaves , bare. In that format, a certificate with no emailAddress attribute, or whose poisoned CN precedes it, authenticates as the smuggled address. Formats that quote a value instead of escaping its characters, such as OpenSSL's XN_FLAG_ONELINE (CN = "jdoe,emailAddress=admin@example.com"), are affected the same way.

On Symfony 5.4, the deprecated X509AuthenticationListener, which SecurityBundle uses when security.enable_authenticator_manager is not set to true (the default on 5.4), still matched emailAddress= anywhere in the DN: the fix for CVE-2026-45063 did not cover it, so applications on that code path were also exposed to the original report.

An attacker holding a certificate from the application's trusted CA who can influence the CN authenticates as an arbitrary user of the application, including a privileged one.

Applications that pass a per-attribute userKey, such as Apache's SSL_CLIENT_S_DN_Email, never reach this fallback and are not affected, and neither are deployments whose CA generates or validates the subject itself rather than accepting requester-supplied values. The nginx configuration Symfony documents does reach the fallback, because nginx exposes no per-attribute equivalent.

Resolution

X509Authenticator and X509AuthenticationListener now split the DN into RDNs before looking for the attribute, instead of matching the attribute against the serialized string. They select the separator from the serialization (X509_NAME_oneline() always prefixes the DN with a slash, RFC 2253 never does), split on unescaped occurrences of that separator only, unescape each RDN afterwards with the rules of its format, and require the attribute to span a whole RDN. A separator inside a quoted value doesn't split the DN either. The accepted value grammar is unchanged.

X509_NAME_oneline() as implemented by OpenSSL before 3.0, and HAProxy's ssl_c_s_dn fetch, write a slash inside a value without escaping it, so such a DN cannot be parsed unambiguously by anyone. Deployments using Apache's LegacyDNStringFormat or nginx's $ssl_client_s_dn_legacy with those libraries, or HAProxy's ssl_c_s_dn, should switch to the RFC 2253 output ($ssl_client_s_dn, Apache's default, ssl_c_s_dn(,rfc2253)) or pass a per-attribute value as the user key (SSL_CLIENT_S_DN_Email, ssl_c_s_dn(emailAddress)).

One behaviour change is worth noting for reviewers: a slash-separated DN now has to start with a slash to be read as slash-separated. CN=jdoe/emailAddress=admin@example.com is byte for byte both the attack and a two-RDN slash-separated DN written without a leading slash, so one of the two readings had to go, and X509_NAME_oneline() always emits the leading slash.

The patches for this issue are available here for branch 5.4 and here for branch 6.4 (and forward-ported to 7.4, 8.1 and 8.2). From 6.4 on, the identifier attribute is configurable through credentialUserIdentifier, and its value grammar accepts any value rather than an address, so the custom spelling was spoofable the same way. Both arms of the pattern are anchored to a whole RDN.

Credits

We would like to thank Oryam Halachmi, dahmani toumi and Stapat for reporting the issue.

We would also like to thank Bruno Dias and João Oliveira from Checkmarx for their help getting a CVE assigned.