CVE-2026-41003
This Vulnerability has been fixed in the Never-Ending Support (NES) version offered by HeroDevs.
Overview
Spring Security is the de facto authentication and access-control framework for Spring applications, providing login, authorization, CSRF protection, and protocol integrations including SAML 2.0 single sign-on. Its spring-security-saml2-service-provider module lets an application act as a SAML 2.0 service provider, exchanging authentication and logout messages with an identity provider (the asserting party) described by a RelyingPartyRegistration. When the SAML POST binding is used, the relevant Spring Security servlet filters return a small self-submitting HTML form that auto-posts the SAML message to the identity provider's service location.
A high-severity vulnerability (CVE-2026-41003) has been identified in how those filters build that HTML page. The form action URL is taken from the RelyingPartyRegistration (the asserting party's single sign-on or single logout service location) and concatenated into the page without HTML encoding, even though the SAML message and relay-state values in the same form are escaped. An attacker who is able to influence the values in a RelyingPartyRegistration, for example by controlling or tampering with the asserting-party metadata the service provider ingests, can break out of the action attribute and inject arbitrary markup or script into the page in the service provider’s origin.
Per OWASP, Cross-Site Scripting (XSS) is an attack in which "malicious scripts are injected into otherwise benign and trusted websites," allowing an attacker to "send malicious code, generally in the form of a browser side script, to a different end user." Here the injected payload is the relying party's service-location URL: because it is written into the form action attribute without encoding, a value containing a quote and a script tag escapes the attribute and runs in the user's session.
The CVSS v3.1 vector for this vulnerability is AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:L/A:N (High, base score 7.6). The attack vector is Network and complexity is Low; the attacker needs the limited privilege of influencing relying-party values and the victim must load the generated page; the injected content then runs in the service provider's origin, hence the high confidentiality impact and changed scope.
This issue affects >=5.5.0 <=5.5.8, >=5.7.0 <=5.7.23, >=5.8.0 <=5.8.25, >=6.2.0 <=6.2.8, >=6.3.0 <=6.3.16, >=6.4.0 <=6.4.16, >=6.5.0 <=6.5.10, and >=7.0.0 <=7.0.5 of Spring Security.
Details
Module Info
- Product: Spring Security
- Affected packages: spring-security-saml2-service-provider
- Affected versions: >=5.5.0 <=5.5.8, >=5.7.0 <=5.7.23, >=5.8.0 <=5.8.25, >=6.2.0 <=6.2.8, >=6.3.0 <=6.3.16, >=6.4.0 <=6.4.16, >=6.5.0 <=6.5.10, and >=7.0.0 <=7.0.5
- GitHub repository: https://github.com/spring-projects/spring-security
- Published packages: https://central.sonatype.com/artifact/org.springframework.security/spring-security-saml2-service-provider
- Package manager: Maven
- Fixed in:
- NES for Spring Security 5.5.x, 5.7.x, 5.8.x, 6.2.x, 6.3.x, 6.4.x
- Spring Security 6.5.11, 7.0.6 (OSS)
Vulnerability Info
The vulnerability is in the SAML 2.0 service-provider filters that implement the SAML POST binding: Saml2WebSsoAuthenticationRequestFilter for outbound authentication requests, and Saml2LogoutRequestFilter and Saml2RelyingPartyInitiatedLogoutSuccessHandler for logout. Each builds a self-submitting HTML form in a private createSamlPostRequestFormData method by appending fixed HTML fragments and request-derived values to a StringBuilder.
The values that originate from the SAML message itself, SAMLRequest, SAMLResponse, and RelayState, are passed through HtmlUtils.htmlEscape(...) before being written into the hidden <input> fields. The form action URL, however, is appended directly:
String authenticationRequestUri = authenticationRequest.getAuthenticationRequestUri();
StringBuilder html = new StringBuilder();
...
html.append(" <form action=\"");
html.append(authenticationRequestUri);
html.append("\" method=\"post\">\n");
...
html.append(" <input type=\"hidden\" name=\"SAMLRequest\" value=\"");
html.append(HtmlUtils.htmlEscape(samlRequest));
html.append("\"/>\n");
The authenticationRequestUri (and, in the logout filters, the location / response-location value) comes from the RelyingPartyRegistration asserting-party details, which are commonly populated from identity-provider metadata or a dynamic registration repository. When an attacker can influence those values, a service location such as a URL containing a double quote followed by a <script> element terminates the action attribute and injects attacker-controlled HTML into the page. Because the page is served from the service provider's own origin, the attacker can inject arbitrary markup into the victim's session, hijacking the auto-submit form's destination or executing script in the victim's authenticated context.
The fix leverages HTML-encoding in the form action URL before it is written to the page. This is the same HtmlUtils.htmlEscape(...) treatment already applied to the SAML message and relay-state. So, the relying-party URL containing HTML metacharacters can no longer break out of the attribute.
Mitigation
Only recent versions of Spring Security receive community support and updates. Older versions have no publicly available fixes for this vulnerability.
Users of the affected components should apply one of the following mitigations:
- Upgrade to a currently supported version of Spring Security. The OSS fixes are included in Spring Security 6.5.11 (6.5.x line) and 7.0.6 (7.0.x line).
- Leverage a commercial support partner like HeroDevs for post-EOL security support through Never-Ending Support (NES) for Spring Security. Learn more about HeroDevs Never-Ending Support for Spring Security and request coverage at https://www.herodevs.com/support/spring-nes
Credits
- No public credit was provided in the vendor advisory.