Skip to content

Commit 466e11b

Browse files
committed
Merge pull request #50205 from kwondh5217
* fix/whitelabel-timestamp-html-escaping: Polish contribution Apply HTML escaping to timestamp attribute in Whitelabel error page Closes gh-50205
2 parents f280d12 + b1ffa65 commit 466e11b

4 files changed

Lines changed: 75 additions & 5 deletions

File tree

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/error/AbstractErrorWebExceptionHandler.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
package org.springframework.boot.autoconfigure.web.reactive.error;
1818

1919
import java.util.Collections;
20-
import java.util.Date;
2120
import java.util.List;
2221
import java.util.Map;
2322

@@ -237,17 +236,17 @@ private Resource resolveResource(String viewName) {
237236
protected Mono<ServerResponse> renderDefaultErrorView(ServerResponse.BodyBuilder responseBody,
238237
Map<String, Object> error) {
239238
StringBuilder builder = new StringBuilder();
240-
Date timestamp = (Date) error.get("timestamp");
239+
Object timestamp = error.get("timestamp");
241240
Object message = error.get("message");
242241
Object trace = error.get("trace");
243242
Object requestId = error.get("requestId");
244243
builder.append("<html><body><h1>Whitelabel Error Page</h1>")
245244
.append("<p>This application has no configured error view, so you are seeing this as a fallback.</p>")
246245
.append("<div id='created'>")
247-
.append(timestamp)
246+
.append(htmlEscape(timestamp))
248247
.append("</div>")
249248
.append("<div>[")
250-
.append(requestId)
249+
.append(htmlEscape(requestId))
251250
.append("] There was an unexpected error (type=")
252251
.append(htmlEscape(error.get("error")))
253252
.append(", status=")

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/error/ErrorMvcAutoConfiguration.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ public void render(Map<String, ?> model, HttpServletRequest request, HttpServlet
213213
builder.append("<html><body><h1>Whitelabel Error Page</h1>")
214214
.append("<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>")
215215
.append("<div id='created'>")
216-
.append(timestamp)
216+
.append(htmlEscape(timestamp))
217217
.append("</div>")
218218
.append("<div>There was an unexpected error (type=")
219219
.append(htmlEscape(model.get("error")))

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/error/DefaultErrorWebExceptionHandlerIntegrationTests.java

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,29 @@ void escapeHtmlInDefaultErrorView() {
460460
});
461461
}
462462

463+
@Test
464+
void escapeHtmlInErrorAttributes() {
465+
this.contextRunner.withPropertyValues("spring.mustache.prefix=classpath:/unknown/")
466+
.withUserConfiguration(CustomErrorAttributesWithEscaping.class)
467+
.run((context) -> {
468+
WebTestClient client = getWebClient(context);
469+
String body = client.get()
470+
.uri("/")
471+
.accept(MediaType.TEXT_HTML)
472+
.exchange()
473+
.expectStatus()
474+
.isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR)
475+
.expectHeader()
476+
.contentType(TEXT_HTML_UTF8)
477+
.expectBody(String.class)
478+
.returnResult()
479+
.getResponseBody();
480+
assertThat(body).doesNotContain("<script>")
481+
.contains("&lt;script&gt;")
482+
.contains("xss-error", "xss-message", "xss-requestId", "xss-timestamp", "xss-trace");
483+
});
484+
}
485+
463486
@Test
464487
void testExceptionWithNullMessage() {
465488
this.contextRunner.withPropertyValues("spring.mustache.prefix=classpath:/unknown/").run((context) -> {
@@ -781,4 +804,27 @@ public Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttrib
781804

782805
}
783806

807+
@Configuration(proxyBeanMethods = false)
808+
static class CustomErrorAttributesWithEscaping {
809+
810+
@Bean
811+
ErrorAttributes errorAttributes() {
812+
return new DefaultErrorAttributes() {
813+
814+
@Override
815+
public Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) {
816+
Map<String, Object> attributes = new LinkedHashMap<>(super.getErrorAttributes(request, options));
817+
attributes.put("error", "<script>alert('xss-error')</script>");
818+
attributes.put("message", "<script>alert('xss-message')</script>");
819+
attributes.put("requestId", "<script>alert('xss-requestId')</script>");
820+
attributes.put("timestamp", "<script>alert('xss-timestamp')</script>");
821+
attributes.put("trace", "<script>alert('xss-trace')</script>");
822+
return attributes;
823+
}
824+
825+
};
826+
}
827+
828+
}
829+
784830
}

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/error/ErrorMvcAutoConfigurationTests.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.time.Clock;
2020
import java.util.Map;
2121

22+
import jakarta.servlet.http.HttpServletResponse;
2223
import org.junit.jupiter.api.Test;
2324
import org.junit.jupiter.api.extension.ExtendWith;
2425

@@ -84,6 +85,30 @@ void renderCanUseJavaTimeTypeAsTimestamp() { // gh-23256
8485
});
8586
}
8687

88+
@Test
89+
void renderEscapesHtmlInErrorAttributes() {
90+
this.contextRunner.run((context) -> {
91+
View errorView = context.getBean("error", View.class);
92+
ErrorAttributes errorAttributes = context.getBean(ErrorAttributes.class);
93+
DispatcherServletWebRequest webRequest = createWebRequest(new IllegalStateException("Exception message"),
94+
false);
95+
Map<String, Object> attributes = errorAttributes.getErrorAttributes(webRequest, withAllOptions());
96+
attributes.put("error", "<script>alert('xss-error')</script>");
97+
attributes.put("message", "<script>alert('xss-message')</script>");
98+
attributes.put("timestamp", "<script>alert('xss-timestamp')</script>");
99+
attributes.put("trace", "<script>alert('xss-trace')</script>");
100+
HttpServletResponse response = webRequest.getResponse();
101+
assertThat(response).isNotNull();
102+
errorView.render(attributes, webRequest.getRequest(), response);
103+
String responseString = ((MockHttpServletResponse) response).getContentAsString();
104+
assertThat(responseString).contains("&lt;script&gt;alert(&#39;xss-error&#39;)&lt;/script&gt;")
105+
.contains("&lt;script&gt;alert(&#39;xss-message&#39;)&lt;/script&gt;")
106+
.contains("&lt;script&gt;alert(&#39;xss-timestamp&#39;)&lt;/script&gt;")
107+
.contains("&lt;script&gt;alert(&#39;xss-trace&#39;)&lt;/script&gt;")
108+
.doesNotContain("<script>");
109+
});
110+
}
111+
87112
@Test
88113
void renderWhenAlreadyCommittedLogsMessage(CapturedOutput output) {
89114
this.contextRunner.run((context) -> {

0 commit comments

Comments
 (0)