From f273f5ec351baf6e4fecfb09a343cd64617bc5b1 Mon Sep 17 00:00:00 2001 From: Daeho Kwon Date: Fri, 24 Apr 2026 23:22:04 +0900 Subject: [PATCH] Apply HTML escaping to timestamp attribute in Whitelabel error page See gh-50205 Signed-off-by: Daeho Kwon --- .../AbstractErrorWebExceptionHandler.java | 7 ++- .../error/ErrorMvcAutoConfiguration.java | 2 +- ...orWebExceptionHandlerIntegrationTests.java | 43 +++++++++++++++++++ .../error/ErrorMvcAutoConfigurationTests.java | 19 ++++++++ 4 files changed, 66 insertions(+), 5 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/error/AbstractErrorWebExceptionHandler.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/error/AbstractErrorWebExceptionHandler.java index 08892762e98..aed7e40a01b 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/error/AbstractErrorWebExceptionHandler.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/error/AbstractErrorWebExceptionHandler.java @@ -17,7 +17,6 @@ package org.springframework.boot.autoconfigure.web.reactive.error; import java.util.Collections; -import java.util.Date; import java.util.List; import java.util.Map; @@ -237,17 +236,17 @@ public abstract class AbstractErrorWebExceptionHandler implements ErrorWebExcept protected Mono renderDefaultErrorView(ServerResponse.BodyBuilder responseBody, Map error) { StringBuilder builder = new StringBuilder(); - Date timestamp = (Date) error.get("timestamp"); + Object timestamp = error.get("timestamp"); Object message = error.get("message"); Object trace = error.get("trace"); Object requestId = error.get("requestId"); builder.append("

Whitelabel Error Page

") .append("

This application has no configured error view, so you are seeing this as a fallback.

") .append("
") - .append(timestamp) + .append(htmlEscape(timestamp)) .append("
") .append("
[") - .append(requestId) + .append(htmlEscape(requestId)) .append("] There was an unexpected error (type=") .append(htmlEscape(error.get("error"))) .append(", status=") diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/error/ErrorMvcAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/error/ErrorMvcAutoConfiguration.java index fa107c415c5..7241d12e508 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/error/ErrorMvcAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/error/ErrorMvcAutoConfiguration.java @@ -213,7 +213,7 @@ public class ErrorMvcAutoConfiguration { builder.append("

Whitelabel Error Page

") .append("

This application has no explicit mapping for /error, so you are seeing this as a fallback.

") .append("
") - .append(timestamp) + .append(htmlEscape(timestamp)) .append("
") .append("
There was an unexpected error (type=") .append(htmlEscape(model.get("error"))) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/error/DefaultErrorWebExceptionHandlerIntegrationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/error/DefaultErrorWebExceptionHandlerIntegrationTests.java index 5f9364c04c7..04c6438715e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/error/DefaultErrorWebExceptionHandlerIntegrationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/error/DefaultErrorWebExceptionHandlerIntegrationTests.java @@ -460,6 +460,27 @@ class DefaultErrorWebExceptionHandlerIntegrationTests { }); } + @Test + void escapeHtmlInTimestampAndRequestIdAttributes() { + this.contextRunner.withPropertyValues("spring.mustache.prefix=classpath:/unknown/") + .withUserConfiguration(CustomErrorAttributesWithHtmlInTimestampAndRequestId.class) + .run((context) -> { + WebTestClient client = getWebClient(context); + String body = client.get() + .uri("/") + .accept(MediaType.TEXT_HTML) + .exchange() + .expectStatus() + .isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR) + .expectHeader() + .contentType(TEXT_HTML_UTF8) + .expectBody(String.class) + .returnResult() + .getResponseBody(); + assertThat(body).doesNotContain(""); + attributes.put("requestId", ""); + return attributes; + } + + }; + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/ErrorMvcAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/ErrorMvcAutoConfigurationTests.java index 36fa0607f4b..ac027f3e55e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/ErrorMvcAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/ErrorMvcAutoConfigurationTests.java @@ -19,6 +19,7 @@ package org.springframework.boot.autoconfigure.web.servlet.error; import java.time.Clock; import java.util.Map; +import jakarta.servlet.http.HttpServletResponse; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -84,6 +85,24 @@ class ErrorMvcAutoConfigurationTests { }); } + @Test + void renderEscapesHtmlInTimestampAttribute() { + this.contextRunner.run((context) -> { + View errorView = context.getBean("error", View.class); + ErrorAttributes errorAttributes = context.getBean(ErrorAttributes.class); + DispatcherServletWebRequest webRequest = createWebRequest(new IllegalStateException("Exception message"), + false); + Map attributes = errorAttributes.getErrorAttributes(webRequest, withAllOptions()); + attributes.put("timestamp", ""); + HttpServletResponse response = webRequest.getResponse(); + assertThat(response).isNotNull(); + errorView.render(attributes, webRequest.getRequest(), response); + String responseString = ((MockHttpServletResponse) response).getContentAsString(); + assertThat(responseString).contains("<script>alert('xss')</script>") + .doesNotContain("