VYPR
Medium severity6.8NVD Advisory· Published Apr 22, 2026· Updated Apr 24, 2026

CVE-2026-22747

CVE-2026-22747

Description

Vulnerability in Spring Spring Security. SubjectX500PrincipalExtractor does not correctly handle certain malformed X.509 certificate CN values, which can lead to reading the wrong value for the username. In a carefully crafted certificate, this can lead to an attacker impersonating another user. This issue affects Spring Security: from 7.0.0 through 7.0.4.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
org.springframework.security:spring-security-webMaven
>= 7.0.0, < 7.0.57.0.5

Affected products

1

Patches

1
88118afb8f93

Use RDN Parsing

3 files changed · +96 14
  • web/src/main/java/org/springframework/security/web/authentication/preauth/x509/SubjectX500PrincipalExtractor.java+30 14 modified
    @@ -17,9 +17,11 @@
     package org.springframework.security.web.authentication.preauth.x509;
     
     import java.security.cert.X509Certificate;
    -import java.util.regex.Matcher;
    -import java.util.regex.Pattern;
    +import java.util.List;
     
    +import javax.naming.InvalidNameException;
    +import javax.naming.ldap.LdapName;
    +import javax.naming.ldap.Rdn;
     import javax.security.auth.x500.X500Principal;
     
     import org.apache.commons.logging.Log;
    @@ -47,14 +49,13 @@ public final class SubjectX500PrincipalExtractor implements X509PrincipalExtract
     
     	private final Log logger = LogFactory.getLog(getClass());
     
    -	private static final Pattern EMAIL_SUBJECT_DN_PATTERN = Pattern.compile("OID.1.2.840.113549.1.9.1=(.*?)(?:,|$)",
    -			Pattern.CASE_INSENSITIVE);
    +	private static final String EMAIL_SUBJECT_DN_TYPE = "OID.1.2.840.113549.1.9.1";
     
    -	private static final Pattern CN_SUBJECT_DN_PATTERN = Pattern.compile("CN=(.*?)(?:,|$)", Pattern.CASE_INSENSITIVE);
    +	private static final String CN_SUBJECT_DN_TYPE = "CN";
     
     	private MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
     
    -	private Pattern subjectDnPattern = CN_SUBJECT_DN_PATTERN;
    +	private String subjectDnType = CN_SUBJECT_DN_TYPE;
     
     	private String x500PrincipalFormat = X500Principal.RFC2253;
     
    @@ -64,16 +65,31 @@ public Object extractPrincipal(X509Certificate clientCert) {
     		X500Principal principal = clientCert.getSubjectX500Principal();
     		String subjectDN = principal.getName(this.x500PrincipalFormat);
     		this.logger.debug(LogMessage.format("Subject DN is '%s'", subjectDN));
    -		Matcher matcher = this.subjectDnPattern.matcher(subjectDN);
    -		if (!matcher.find()) {
    -			throw new BadCredentialsException(this.messages.getMessage("SubjectX500PrincipalExtractor.noMatching",
    -					new Object[] { subjectDN }, "No matching pattern was found in subject DN: {0}"));
    -		}
    -		String principalName = matcher.group(1);
    +		String principalName = getSubject(subjectDN);
     		this.logger.debug(LogMessage.format("Extracted Principal name is '%s'", principalName));
     		return principalName;
     	}
     
    +	private List<Rdn> getDns(String subjectDn) {
    +		try {
    +			return new LdapName(subjectDn).getRdns();
    +		}
    +		catch (InvalidNameException ex) {
    +			throw new BadCredentialsException("Failed to parse client certificate", ex);
    +		}
    +	}
    +
    +	private String getSubject(String subjectDn) {
    +		for (Rdn rdn : getDns(subjectDn)) {
    +			String type = rdn.getType();
    +			if (this.subjectDnType.equals(type)) {
    +				return String.valueOf(rdn.getValue());
    +			}
    +		}
    +		throw new BadCredentialsException(this.messages.getMessage("SubjectX500PrincipalExtractor.noMatching",
    +				new Object[] { subjectDn }, "No matching pattern was found in subject DN: {0}"));
    +	}
    +
     	@Override
     	public void setMessageSource(MessageSource messageSource) {
     		Assert.notNull(messageSource, "messageSource cannot be null");
    @@ -104,11 +120,11 @@ public void setMessageSource(MessageSource messageSource) {
     	 */
     	public void setExtractPrincipalNameFromEmail(boolean extractPrincipalNameFromEmail) {
     		if (extractPrincipalNameFromEmail) {
    -			this.subjectDnPattern = EMAIL_SUBJECT_DN_PATTERN;
    +			this.subjectDnType = EMAIL_SUBJECT_DN_TYPE;
     			this.x500PrincipalFormat = X500Principal.RFC1779;
     		}
     		else {
    -			this.subjectDnPattern = CN_SUBJECT_DN_PATTERN;
    +			this.subjectDnType = CN_SUBJECT_DN_TYPE;
     			this.x500PrincipalFormat = X500Principal.RFC2253;
     		}
     	}
    
  • web/src/test/java/org/springframework/security/web/authentication/preauth/x509/SubjectX500PrincipalExtractorTests.java+16 0 modified
    @@ -53,6 +53,22 @@ void extractWhenCnAtEndThenExtractsPrincipalName() throws Exception {
     		assertThat(principal).isEqualTo("Duke");
     	}
     
    +	@Test
    +	void extractWhenDnEmbeddedInCnThenExtractsPrincipalName() throws Exception {
    +		Object principal = this.extractor.extractPrincipal(X509TestUtils.buildTestCertficateWithEmbeddedDn());
    +
    +		assertThat(principal).isEqualTo("luke");
    +	}
    +
    +	@Test
    +	void extractWhenEmailDnEmbeddedInCnThenExtractsEmail() throws Exception {
    +		this.extractor.setExtractPrincipalNameFromEmail(true);
    +
    +		Object principal = this.extractor.extractPrincipal(X509TestUtils.buildTestCertficateWithEmbeddedEmailDn());
    +
    +		assertThat(principal).isEqualTo("luke@monkeymachine");
    +	}
    +
     	@Test
     	void setMessageSourceWhenNullThenThrowsException() {
     		assertThatIllegalArgumentException().isThrownBy(() -> this.extractor.setMessageSource(null));
    
  • web/src/test/java/org/springframework/security/web/authentication/preauth/x509/X509TestUtils.java+50 0 modified
    @@ -135,4 +135,54 @@ public static X509Certificate buildTestCertificateWithCnAtEnd() throws Exception
     		return (X509Certificate) cf.generateCertificate(in);
     	}
     
    +	public static X509Certificate buildTestCertficateWithEmbeddedDn() throws Exception {
    +		String cert = "-----BEGIN CERTIFICATE-----\n"
    +				+ "MIIDDTCCAfWgAwIBAgIJANSyvk4gJhqPMA0GCSqGSIb3DQEBCwUAMEYxDTALBgNV\n"
    +				+ "BAMMBGx1a2UxETAPBgNVBAsMCENOPWR1a2UsMRUwEwYDVQQKDAxFeGFtcGxlIENv\n"
    +				+ "cnAxCzAJBgNVBAYTAlVTMB4XDTI2MDEwNDE5MjY0N1oXDTI3MDEwNTE5MjY0N1ow\n"
    +				+ "RjENMAsGA1UEAwwEbHVrZTERMA8GA1UECwwIQ049ZHVrZSwxFTATBgNVBAoMDEV4\n"
    +				+ "YW1wbGUgQ29ycDELMAkGA1UEBhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw\n"
    +				+ "ggEKAoIBAQDU9fY74nEFbBKfIef7CK02J/BJb42sIF9kD8eHN5OvEwLQBeTh30it\n"
    +				+ "E7LLalXyOXeUFkPe1N1ZhGdVak9udsIqULSvQaWqTbN+IrAGklZAxuXYTC1GbhMF\n"
    +				+ "AkGWWM55J2SNqVGQaHzZUn6VPxWaDft6nZR0DxuvXMYM5kVG6VErdB3ygGUv8cjQ\n"
    +				+ "QBKAYpsZeRldnauRPt2dImmGTagvSuJVyr8X/AioE2Rl0guii456AKw+QSvRiZ+g\n"
    +				+ "w08Y8C9nDyzQmurqpdYYkp0X+4yqm1iVowMX+tSPvHnlqJdvVzaW2b0yRzrrT6ao\n"
    +				+ "UCgw25slR1P1IcyzqPKWQIoQRnYIaX1bAgMBAAEwDQYJKoZIhvcNAQELBQADggEB\n"
    +				+ "AIos+nr8DFM6bAt9AI/79O/12hcN7gVv4F3P4Vz6NRRkkvsb9WMN8fLLDEsEJ/BQ\n"
    +				+ "eQkAVnhlmAe++vrqy8OTHoQ7F5C3K0zrr19NLNoyNFTkXkFgnm4ZhYinSbusuIb7\n"
    +				+ "LPYoyCnEEiMdl0VMWWSWcOvZpipbvTtH3CiVxTqXLjFFNraEAyUN50kXjo/zuHpK\n"
    +				+ "HzTS1BAu0li9GdV3Da2ELdDx90zaUym7dDIejY4YUlXYIJ5UUYS61fqtgOHGLLdb\n"
    +				+ "UXGAr5gqEe7OrQ9D4ebg9w5ciTb7g1H2CmirjTf/rkii8AojmsGFKIfGVe3gY6EB\n" + "o9eF5FV9V9leo5yLo25ev08=\n"
    +				+ "-----END CERTIFICATE-----";
    +		ByteArrayInputStream in = new ByteArrayInputStream(cert.getBytes());
    +		CertificateFactory cf = CertificateFactory.getInstance("X.509");
    +		return (X509Certificate) cf.generateCertificate(in);
    +	}
    +
    +	public static X509Certificate buildTestCertficateWithEmbeddedEmailDn() throws Exception {
    +		String cert = "-----BEGIN CERTIFICATE-----\n"
    +				+ "MIIDfDCCAmSgAwIBAgIIXHoOUFeZ29MwDQYJKoZIhvcNAQELBQAwfjEhMB8GCSqG\n"
    +				+ "SIb3DQEJARYSbHVrZUBtb25rZXltYWNoaW5lMTUwMwYDVQQLDCxPSUQuMS4yLjg0\n"
    +				+ "MC4xMTM1NDkuMS45LjE9ZHVrZUBnb3JpbGxhZ2FkZ2V0LDEVMBMGA1UECgwMRXhh\n"
    +				+ "bXBsZSBDb3JwMQswCQYDVQQGEwJVUzAeFw0yNjAxMDQxOTMxMDhaFw0yNzAxMDUx\n"
    +				+ "OTMxMDhaMH4xITAfBgkqhkiG9w0BCQEWEmx1a2VAbW9ua2V5bWFjaGluZTE1MDMG\n"
    +				+ "A1UECwwsT0lELjEuMi44NDAuMTEzNTQ5LjEuOS4xPWR1a2VAZ29yaWxsYWdhZGdl\n"
    +				+ "dCwxFTATBgNVBAoMDEV4YW1wbGUgQ29ycDELMAkGA1UEBhMCVVMwggEiMA0GCSqG\n"
    +				+ "SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDBuIQWnj+uvvG+4ZIyFMs4dSbiBavubCmC\n"
    +				+ "hudrHr93hP19QbPulbHRTVCUqEi8efvq+J9jmMdPd7tziuDX02PeG9uljp9+c5Ir\n"
    +				+ "pw9/oMoTRkF7K4PK1JxLN4tcgxjxVA4QkS+MjKLPeHrYyGCjKspcHbi+zBiQ9Xqp\n"
    +				+ "yHWq6N5XPd6mEj2gh0zamnsJCeUCOX4SJbcp3MFtcYzhguHAeVhy9Jv+EAMJejDn\n"
    +				+ "YIZmMUdP6Ykf2zTzs/4L3bRZb0oS5WvfeRdJB6SKg8mNO/jdGX87krSio//cRdDy\n"
    +				+ "TGQK+YCVDf8GyLLavYZW56AJbZxL3MWgHYilQjj4p+Kw/PWpaBVvAgMBAAEwDQYJ\n"
    +				+ "KoZIhvcNAQELBQADggEBAKVTMIo8JO0H0HRrpsEDP17E2pnfMJV4g70BwClUMMek\n"
    +				+ "wNIWZn+6XPR8oObzzjnVWXjrovMkmmyFk0vWIpF68MPyiQ++5fwdzOZiQtUP177n\n"
    +				+ "9ulAtLoIJld3olGeL9VsCZGp3J2PqiDe613zd+bkSUG1lQYC2awozWqJEdvwJJtf\n"
    +				+ "j9nlhyMsARKEEu3tFGJsCHST3XhbhFKOraf/GZ21xW650R7ap0ZNaEiB16M2a5Oe\n"
    +				+ "WXasgUukIo82Z8+yK4IITeCcr0aA1fJxwhU8J6qfYWloaoirSYj487HRnPPv3X/b\n"
    +				+ "RxZynIjtGKygT6T1dRaWennmoitqfprJnEO2tlhLwP0=\n" + "-----END CERTIFICATE-----";
    +		ByteArrayInputStream in = new ByteArrayInputStream(cert.getBytes());
    +		CertificateFactory cf = CertificateFactory.getInstance("X.509");
    +		return (X509Certificate) cf.generateCertificate(in);
    +	}
    +
     }
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

4

News mentions

0

No linked articles in our index yet.