Decode percent-encoded values

Closes gh-19136

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
This commit is contained in:
Josh Cummings 2026-04-29 08:57:16 -06:00
parent 6343002b32
commit b075f0df02
2 changed files with 21 additions and 2 deletions

View File

@ -17,6 +17,7 @@
package org.springframework.security.web;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.List;
import java.util.Map.Entry;
@ -30,6 +31,7 @@ import org.springframework.security.crypto.keygen.Base64StringKeyGenerator;
import org.springframework.security.crypto.keygen.StringKeyGenerator;
import org.springframework.web.util.HtmlUtils;
import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.web.util.UriUtils;
/**
* Redirect using an auto-submitting HTML form using the POST method. All query params
@ -83,8 +85,12 @@ public final class FormPostRedirectStrategy implements RedirectStrategy {
final StringBuilder hiddenInputsHtmlBuilder = new StringBuilder();
for (final Entry<String, List<String>> entry : uriComponentsBuilder.build().getQueryParams().entrySet()) {
final String name = entry.getKey();
for (final String value : entry.getValue()) {
final String name = UriUtils.decode(entry.getKey(), StandardCharsets.UTF_8);
for (final String raw : entry.getValue()) {
if (raw == null) {
continue;
}
final String value = UriUtils.decode(raw, StandardCharsets.UTF_8);
// @formatter:off
final String hiddenInput = HIDDEN_INPUT_TEMPLATE
.replace("{{name}}", HtmlUtils.htmlEscape(name))

View File

@ -101,6 +101,19 @@ public class FormPostRedirectStrategyTests {
assertThat(this.response).satisfies(hasScriptSrcNonce());
}
// gh-19136
@Test
public void absoluteUrlWithPercentEncodedQueryParamsRedirect() throws IOException {
this.redirectStrategy.sendRedirect(this.request, this.response, "https://example.com/cb?payload=a%2Bb%2Fc%3D");
assertThat(this.response.getStatus()).isEqualTo(HttpStatus.OK.value());
assertThat(this.response.getContentType()).isEqualTo(MediaType.TEXT_HTML_VALUE);
assertThat(this.response.getContentAsString()).contains("action=\"https://example.com/cb\"");
assertThat(this.response.getContentAsString())
.contains("<input name=\"payload\" type=\"hidden\" value=\"a+b/c=\" />");
assertThat(this.response).satisfies(hasScriptSrcNonce());
}
private ThrowingConsumer<MockHttpServletResponse> hasScriptSrcNonce() {
return (response) -> {
final String policyDirective = response.getHeader("Content-Security-Policy");