Apply HTML escaping to timestamp attribute in Whitelabel error page

See gh-50205

Signed-off-by: Daeho Kwon <trewq231@naver.com>
This commit is contained in:
Daeho Kwon
2026-04-25 10:10:56 +02:00
committed by Stéphane Nicoll
parent f280d12729
commit f273f5ec35
4 changed files with 66 additions and 5 deletions
@@ -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<ServerResponse> renderDefaultErrorView(ServerResponse.BodyBuilder responseBody,
Map<String, Object> 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("<html><body><h1>Whitelabel Error Page</h1>")
.append("<p>This application has no configured error view, so you are seeing this as a fallback.</p>")
.append("<div id='created'>")
.append(timestamp)
.append(htmlEscape(timestamp))
.append("</div>")
.append("<div>[")
.append(requestId)
.append(htmlEscape(requestId))
.append("] There was an unexpected error (type=")
.append(htmlEscape(error.get("error")))
.append(", status=")
@@ -213,7 +213,7 @@ public class ErrorMvcAutoConfiguration {
builder.append("<html><body><h1>Whitelabel Error Page</h1>")
.append("<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>")
.append("<div id='created'>")
.append(timestamp)
.append(htmlEscape(timestamp))
.append("</div>")
.append("<div>There was an unexpected error (type=")
.append(htmlEscape(model.get("error")))
@@ -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("<script>").contains("&lt;script&gt;");
});
}
@Test
void testExceptionWithNullMessage() {
this.contextRunner.withPropertyValues("spring.mustache.prefix=classpath:/unknown/").run((context) -> {
@@ -781,4 +802,26 @@ class DefaultErrorWebExceptionHandlerIntegrationTests {
}
@Configuration(proxyBeanMethods = false)
static class CustomErrorAttributesWithHtmlInTimestampAndRequestId {
@Bean
ErrorAttributes errorAttributes() {
return new DefaultErrorAttributes() {
@Override
public Map<String, Object> getErrorAttributes(ServerRequest request,
ErrorAttributeOptions options) {
Map<String, Object> attributes = new LinkedHashMap<>(
super.getErrorAttributes(request, options));
attributes.put("timestamp", "<script>alert('xss')</script>");
attributes.put("requestId", "<script>alert('xss')</script>");
return attributes;
}
};
}
}
}
@@ -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<String, Object> attributes = errorAttributes.getErrorAttributes(webRequest, withAllOptions());
attributes.put("timestamp", "<script>alert('xss')</script>");
HttpServletResponse response = webRequest.getResponse();
assertThat(response).isNotNull();
errorView.render(attributes, webRequest.getRequest(), response);
String responseString = ((MockHttpServletResponse) response).getContentAsString();
assertThat(responseString).contains("&lt;script&gt;alert(&#39;xss&#39;)&lt;/script&gt;")
.doesNotContain("<script>");
});
}
@Test
void renderWhenAlreadyCommittedLogsMessage(CapturedOutput output) {
this.contextRunner.run((context) -> {