Security context
Medium· 5.9GHSA-g8hw-794c-4j9g CVE-2018-1271CWE-22Published Oct 17, 2018

Path Traversal in org.springframework:spring-core

Research this vulnerability

Research is free — Hunters explains how the bug works, the root-cause code pattern, how the fix addresses it, and how to test whether a target is affected, in chat. Investigate & write exploit is a paid run — the engine reads the advisory and fix commits, then builds and validates a working proof-of-concept exploit with reproduction steps.

Affected versions

5.0.0 → fixed in 5.0.50 → fixed in 4.3.15

Details

Spring Framework, versions 5.0 prior to 5.0.5 and versions 4.3 prior to 4.3.15 and older unsupported versions, allow applications to configure Spring MVC to serve static resources (e.g. CSS, JS, images). When static resources are served from a file system on Windows (as opposed to the classpath, or the ServletContext), a malicious user can send a request using a specially crafted URL that can lead a directory traversal attack.

The fix

Clean duplicate separators in resource URLs

Rossen Stoyanchev· Mar 19, 2018, 09:11 PM+231510e28bee0f1
spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceWebHandler.java+48 5
@@ -328,7 +328,15 @@ protected Mono<Resource> getResource(ServerWebExchange exchange) {
if (path.contains("%")) {
try {
// Use URLDecoder (vs UriUtils) to preserve potentially decoded UTF-8 chars
- if (isInvalidPath(URLDecoder.decode(path, "UTF-8"))) {
+ String decodedPath = URLDecoder.decode(path, "UTF-8");
+ if (isInvalidPath(decodedPath)) {
+ if (logger.isTraceEnabled()) {
+ logger.trace("Ignoring invalid resource path with escape sequences [" + path + "].");
+ }
+ return Mono.empty();
+ }
+ decodedPath = processPath(decodedPath);
+ if (isInvalidPath(decodedPath)) {
if (logger.isTraceEnabled()) {
logger.trace("Ignoring invalid resource path with escape sequences [" + path + "].");
}
@@ -352,12 +360,47 @@ protected Mono<Resource> getResource(ServerWebExchange exchange) {
}
/**
- * Process the given resource path to be used.
- * <p>The default implementation replaces any combination of leading '/' and
- * control characters (00-1F and 7F) with a single "/" or "". For example
- * {@code " // /// //// foo/bar"} becomes {@code "/foo/bar"}.
+ * Process the given resource path.
+ * <p>The default implementation replaces:
+ * <ul>
+ * <li>Backslash with forward slash.
+ * <li>Duplicate occurrences of slash with a single slash.
+ * <li>Any combination of leading slash and control characters (00-1F and 7F)
+ * with a single "/" or "". For example {@code " / // foo/bar"}
+ * becomes {@code "/foo/bar"}.
+ * </ul>
+ * @since 3.2.12
*/
protected String processPath(String path) {
+ path = StringUtils.replace(path, "\\", "/");
+ path = cleanDuplicateSlashes(path);
+ return cleanLeadingSlash(path);
+ }
+
+ private String cleanDuplicateSlashes(String path) {
+ StringBuilder sb = null;
+ char prev = 0;
+ for (int i = 0; i < path.length(); i++) {
+ char curr = path.charAt(i);
+ try {
+ if ((curr == '/') && (prev == '/')) {
+ if (sb == null) {
+ sb = new StringBuilder(path.substring(0, i));
+ }
+ continue;
+ }
+ if (sb != null) {
+ sb.append(path.charAt(i));
+ }
+ }
+ finally {
+ prev = curr;
+ }
+ }
+ return sb != null ? sb.toString() : path;
+ }
+
+ private String cleanLeadingSlash(String path) {
boolean slash = false;
for (int i = 0; i < path.length(); i++) {
if (path.charAt(i) == '/') {
spring-webflux/src/test/java/org/springframework/web/reactive/resource/ResourceWebHandlerTests.java+66 23
@@ -54,13 +54,10 @@
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.server.ServerWebExchange;
-import static org.hamcrest.Matchers.instanceOf;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
/**
* Unit tests for {@link ResourceWebHandler}.
@@ -240,39 +237,85 @@ public void getMediaTypeWithFavorPathExtensionOff() throws Exception {
}
@Test
- public void invalidPath() throws Exception {
+ public void testInvalidPath() throws Exception {
+
+ // Use mock ResourceResolver: i.e. we're only testing upfront validations...
+
+ Resource resource = mock(Resource.class);
+ when(resource.getFilename()).thenThrow(new AssertionError("Resource should not be resolved"));
+ when(resource.getInputStream()).thenThrow(new AssertionError("Resource should not be resolved"));
+ ResourceResolver resolver = mock(ResourceResolver.class);
+ when(resolver.resolveResource(any(), any(), any(), any())).thenReturn(Mono.just(resource));
+
+ ResourceWebHandler handler = new ResourceWebHandler();
+ handler.setLocations(Collections.singletonList(new ClassPathResource("test/", getClass())));
+ handler.setResourceResolvers(Collections.singletonList(resolver));
+ handler.afterPropertiesSet();
+
+ testInvalidPath("../testsecret/secret.txt", handler);
+ testInvalidPath("test/../../testsecret/secret.txt", handler);
+ testInvalidPath(":/../../testsecret/secret.txt", handler);
+
+ Resource location = new UrlResource(getClass().getResource("./test/"));
+ this.handler.setLocations(Collections.singletonList(location));
+ Resource secretResource = new UrlResource(getClass().getResource("testsecret/secret.txt"));
+ String secretPath = secretResource.getURL().getPath();
+
+ testInvalidPath("file:" + secretPath, handler);
+ testInvalidPath("/file:" + secretPath, handler);
+ testInvalidPath("url:" + secretPath, handler);
+ testInvalidPath("/url:" + secretPath, handler);
+ testInvalidPath("/../.." + secretPath, handler);
+ testInvalidPath("/%2E%2E/testsecret/secret.txt", handler);
+ testInvalidPath("/%2E%2E/testsecret/secret.txt", handler);
+ testInvalidPath("%2F%2F%2E%2E%2F%2F%2E%2E" + secretPath, handler);
+ }
+
+ private void testInvalidPath(String requestPath, ResourceWebHandler handler) {
+ ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get(""));
+ setPathWithinHandlerMapping(exchange, requestPath);
+ StepVerifier.create(handler.handle(exchange))
+ .expectErrorSatisfies(err -> {
+ assertThat(err, instanceOf(ResponseStatusException.class));
+ assertEquals(HttpStatus.NOT_FOUND, ((ResponseStatusException) err).getStatus());
+ }).verify(TIMEOUT);
+ }
+
+ @Test
+ public void testResolvePathWithTraversal() throws Exception {
for (HttpMethod method : HttpMethod.values()) {
- testInvalidPath(method);
+ testResolvePathWithTraversal(method);
}
}
- private void testInvalidPath(HttpMethod httpMethod) throws Exception {
+ private void testResolvePathWithTraversal(HttpMethod httpMethod) throws Exception {
Resource location = new ClassPathResource("test/", getClass());
this.handler.setLocations(Collections.singletonList(location));
- testInvalidPath(httpMethod, "../testsecret/secret.txt", location);
- testInvalidPath(httpMethod, "test/../../testsecret/secret.txt", location);
- testInvalidPath(httpMethod, ":/../../testsecret/secret.txt", location);
+ testResolvePathWithTraversal(httpMethod, "../testsecret/secret.txt", location);
+ testResolvePathWithTraversal(httpMethod, "test/../../testsecret/secret.txt", location);
+ testResolvePathWithTraversal(httpMethod, ":/../../testsecret/secret.txt", location);
location = new UrlResource(getClass().getResource("./test/"));
this.handler.setLocations(Collections.singletonList(location));
Resource secretResource = new UrlResource(getClass().getResource("testsecret/secret.txt"));
String secretPath = secretResource.getURL().getPath();
- testInvalidPath(httpMethod, "file:" + secretPath, location);
- testInvalidPath(httpMethod, "/file:" + secretPath, location);
- testInvalidPath(httpMethod, "url:" + secretPath, location);
- testInvalidPath(httpMethod, "/url:" + secretPath, location);
- testInvalidPath(httpMethod, "////../.." + secretPath, location);
- testInvalidPath(httpMethod, "/%2E%2E/testsecret/secret.txt", location);
- testInvalidPath(httpMethod, "url:" + secretPath, location);
+ testResolvePathWithTraversal(httpMethod, "file:" + secretPath, location);
+ testResolvePathWithTraversal(httpMethod, "/file:" + secretPath, location);
+ testResolvePathWithTraversal(httpMethod, "url:" + secretPath, location);
+ testResolvePathWithTraversal(httpMethod, "/url:" + secretPath, location);
+ testResolvePathWithTraversal(httpMethod, "////../.." + secretPath, location);
+ testResolvePathWithTraversal(httpMethod, "/%2E%2E/testsecret/secret.txt", location);
+ testResolvePathWithTraversal(httpMethod, "%2F%2F%2E%2E%2F%2Ftestsecret/secret.txt", location);
+ testResolvePathWithTraversal(httpMethod, "url:" + secretPath, location);
// The following tests fail with a MalformedURLException on Windows
- // testInvalidPath(location, "/" + secretPath);
- // testInvalidPath(location, "/ " + secretPath);
+ // testResolvePathWithTraversal(location, "/" + secretPath);
+ // testResolvePathWithTraversal(location, "/ " + secretPath);
}
- private void testInvalidPath(HttpMethod httpMethod, String requestPath, Resource location) throws Exception {
+ private void testResolvePathWithTraversal(HttpMethod httpMethod, String requestPath, Resource location) throws Exception {
ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.method(httpMethod, ""));
setPathWithinHandlerMapping(exchange, requestPath);
StepVerifier.create(this.handler.handle(exchange))
spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java+47 5
@@ -520,7 +520,15 @@ protected Resource getResource(HttpServletRequest request) throws IOException {
if (path.contains("%")) {
try {
// Use URLDecoder (vs UriUtils) to preserve potentially decoded UTF-8 chars
- if (isInvalidPath(URLDecoder.decode(path, "UTF-8"))) {
+ String decodedPath = URLDecoder.decode(path, "UTF-8");
+ if (isInvalidPath(decodedPath)) {
+ if (logger.isTraceEnabled()) {
+ logger.trace("Ignoring invalid resource path with escape sequences [" + path + "].");
+ }
+ return null;
+ }
+ decodedPath = processPath(decodedPath);
+ if (isInvalidPath(decodedPath)) {
if (logger.isTraceEnabled()) {
logger.trace("Ignoring invalid resource path with escape sequences [" + path + "].");
}
@@ -543,13 +551,47 @@ protected Resource getResource(HttpServletRequest request) throws IOException {
}
/**
- * Process the given resource path to be used.
- * <p>The default implementation replaces any combination of leading '/' and
- * control characters (00-1F and 7F) with a single "/" or "". For example
- * {@code " // /// //// foo/bar"} becomes {@code "/foo/bar"}.
+ * Process the given resource path.
+ * <p>The default implementation replaces:
+ * <ul>
+ * <li>Backslash with forward slash.
+ * <li>Duplicate occurrences of slash with a single slash.
+ * <li>Any combination of leading slash and control characters (00-1F and 7F)
+ * with a single "/" or "". For example {@code " / // foo/bar"}
+ * becomes {@code "/foo/bar"}.
+ * </ul>
* @since 3.2.12
*/
protected String processPath(String path) {
+ path = StringUtils.replace(path, "\\", "/");
+ path = cleanDuplicateSlashes(path);
+ return cleanLeadingSlash(path);
+ }
+
+ private String cleanDuplicateSlashes(String path) {
+ StringBuilder sb = null;
+ char prev = 0;
+ for (int i = 0; i < path.length(); i++) {
+ char curr = path.charAt(i);
+ try {
+ if ((curr == '/') && (prev == '/')) {
+ if (sb == null) {
+ sb = new StringBuilder(path.substring(0, i));
+ }
+ continue;
+ }
+ if (sb != null) {
+ sb.append(path.charAt(i));
+ }
+ }
+ finally {
+ prev = curr;
+ }
+ }
+ return sb != null ? sb.toString() : path;
+ }
+
+ private String cleanLeadingSlash(String path) {
boolean slash = false;
for (int i = 0; i < path.length(); i++) {
if (path.charAt(i) == '/') {
spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandlerTests.java+70 18
@@ -43,6 +43,7 @@
import org.springframework.web.servlet.HandlerMapping;
import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
/**
* Unit tests for {@link ResourceHttpRequestHandler}.
@@ -303,41 +304,84 @@ public String getVirtualServerName() {
}
@Test
- public void invalidPath() throws Exception {
+ public void testInvalidPath() throws Exception {
+
+ // Use mock ResourceResolver: i.e. we're only testing upfront validations...
+
+ Resource resource = mock(Resource.class);
+ when(resource.getFilename()).thenThrow(new AssertionError("Resource should not be resolved"));
+ when(resource.getInputStream()).thenThrow(new AssertionError("Resource should not be resolved"));
+ ResourceResolver resolver = mock(ResourceResolver.class);
+ when(resolver.resolveResource(any(), any(), any(), any())).thenReturn(resource);
+
+ ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler();
+ handler.setLocations(Collections.singletonList(new ClassPathResource("test/", getClass())));
+ handler.setResourceResolvers(Collections.singletonList(resolver));
+ handler.setServletContext(new TestServletContext());
+ handler.afterPropertiesSet();
+
+ testInvalidPath("../testsecret/secret.txt", handler);
+ testInvalidPath("test/../../testsecret/secret.txt", handler);
+ testInvalidPath(":/../../testsecret/secret.txt", handler);
+
+ Resource location = new UrlResource(getClass().getResource("./test/"));
+ this.handler.setLocations(Collections.singletonList(location));
+ Resource secretResource = new UrlResource(getClass().getResource("testsecret/secret.txt"));
+ String secretPath = secretResource.getURL().getPath();
+
+ testInvalidPath("file:" + secretPath, handler);
+ testInvalidPath("/file:" + secretPath, handler);
+ testInvalidPath("url:" + secretPath, handler);
+ testInvalidPath("/url:" + secretPath, handler);
+ testInvalidPath("/../.." + secretPath, handler);
+ testInvalidPath("/%2E%2E/testsecret/secret.txt", handler);
+ testInvalidPath("/%2E%2E/testsecret/secret.txt", handler);
+ testInvalidPath("%2F%2F%2E%2E%2F%2F%2E%2E" + secretPath, handler);
+ }
+
+ private void testInvalidPath(String requestPath, ResourceHttpRequestHandler handler) throws Exception {
+ this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, requestPath);
+ this.response = new MockHttpServletResponse();
+ handler.handleRequest(this.request, this.response);
+ assertEquals(HttpStatus.NOT_FOUND.value(), this.response.getStatus());
+ }
+
+ @Test
+ public void resolvePathWithTraversal() throws Exception {
for (HttpMethod method : HttpMethod.values()) {
this.request = new MockHttpServletRequest("GET", "");
this.response = new MockHttpServletResponse();
- testInvalidPath(method);
+ testResolvePathWithTraversal(method);
}
}
- private void testInvalidPath(HttpMethod httpMethod) throws Exception {
+ private void testResolvePathWithTraversal(HttpMethod httpMethod) throws Exception {
this.request.setMethod(httpMethod.name());
Resource location = new ClassPathResource("test/", getClass());
this.handler.setLocations(Collections.singletonList(location));
- testInvalidPath(location, "../testsecret/secret.txt");
- testInvalidPath(location, "test/../../testsecret/secret.txt");
- testInvalidPath(location, ":/../../testsecret/secret.txt");
+ testResolvePathWithTraversal(location, "../testsecret/secret.txt");
+ testResolvePathWithTraversal(location, "test/../../testsecret/secret.txt");
+ testResolvePathWithTraversal(location, ":/../../testsecret/secret.txt");
location = new UrlResource(getClass().getResource("./test/"));
this.handler.setLocations(Collections.singletonList(location));
Resource secretResource = new UrlResource(getClass().getResource("testsecret/secret.txt"));
String secretPath = secretResource.getURL().getPath();
- testInvalidPath(location, "file:" + secretPath);
- testInvalidPath(location, "/file:" + secretPath);
- testInvalidPath(location, "url:" + secretPath);
- testInvalidPath(location, "/url:" + secretPath);
- testInvalidPath(location, "/" + secretPath);
- testInvalidPath(location, "////../.." + secretPath);
- testInvalidPath(location, "/%2E%2E/testsecret/secret.txt");
- testInvalidPath(location, "/ " + secretPath);
- testInvalidPath(location, "url:" + secretPath);
+ testResolvePathWithTraversal(location, "file:" + secretPath);
+ testResolvePathWithTraversal(location, "/file:" + secretPath);
+ testResolvePathWithTraversal(location, "url:" + secretPath);
+ testResolvePathWithTraversal(location, "/url:" + secretPath);
+ testResolvePathWithTraversal(location, "/" + secretPath);
+ testResolvePathWithTraversal(location, "////../.." + secretPath);
+ testResolvePathWithTraversal(location, "/%2E%2E/testsecret/secret.txt");
+ testResolvePathWithTraversal(location, "%2F%2F%2E%2E%2F%2Ftestsecret/secret.txt");
+ testResolvePathWithTraversal(location, "/ " + secretPath);
}
- private void testInvalidPath(Resource location, String requestPath) throws Exception {
+ private void testResolvePathWithTraversal(Resource location, String requestPath) throws Exception {
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, requestPath);
this.response = new MockHttpServletResponse();
this.handler.handleRequest(this.request, this.response);
@@ -356,7 +400,8 @@ public void ignoreInvalidEscapeSequence() throws Exception {
}
@Test
- public void processPath() throws Exception {
+ public void processPath() {
+ // Unchanged
assertSame("/foo/bar", this.handler.processPath("/foo/bar"));
assertSame("foo/bar", this.handler.processPath("foo/bar"));
@@ -382,10 +427,17 @@ public void processPath() throws Exception {
assertEquals("/", this.handler.processPath("/"));
assertEquals("/", this.handler.processPath("///"));
assertEquals("/", this.handler.processPath("/ / / "));
+ assertEquals("/", this.handler.processPath("\\/ \\/ \\/ "));
+
+ // duplicate slash or backslash
+ assertEquals("/foo/ /bar/baz/", this.handler.processPath("//foo/ /bar//baz//"));
+ assertEquals("/foo/ /bar/baz/", this.handler.processPath("\\\\foo\\ \\bar\\\\baz\\\\"));
+ assertEquals("foo/bar", this.handler.processPath("foo\\\\/\\////bar"));
+
}
@Test
- public void initAllowedLocations() throws Exception {
+ public void initAllowedLocations() {
PathResourceResolver resolver = (PathResourceResolver) this.handler.getResourceResolvers().get(0);
Resource[] locations = resolver.getAllowedLocations();

Consistent encoded path evaluation in reactive

Juergen Hoeller· Mar 26, 2018, 11:00 PM+423713356a7ee2
spring-webflux/src/main/java/org/springframework/web/reactive/resource/PathResourceResolver.java+12 11
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2017 the original author or authors.
+ * Copyright 2002-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
package org.springframework.web.reactive.resource;
import java.io.IOException;
+import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Arrays;
import java.util.List;
@@ -184,21 +185,21 @@ else if (resource instanceof ClassPathResource) {
return true;
}
locationPath = (locationPath.endsWith("/") || locationPath.isEmpty() ? locationPath : locationPath + "/");
- if (!resourcePath.startsWith(locationPath)) {
- return false;
- }
+ return (resourcePath.startsWith(locationPath) && !isInvalidEncodedPath(resourcePath));
+ }
+ private boolean isInvalidEncodedPath(String resourcePath) {
if (resourcePath.contains("%")) {
// Use URLDecoder (vs UriUtils) to preserve potentially decoded UTF-8 chars...
- if (URLDecoder.decode(resourcePath, "UTF-8").contains("../")) {
- if (logger.isTraceEnabled()) {
- logger.trace("Resolved resource path contains \"../\" after decoding: " + resourcePath);
- }
- return false;
+ try {
+ String decodedPath = URLDecoder.decode(resourcePath, "UTF-8");
+ return (decodedPath.contains("../") || decodedPath.contains("..\\"));
+ }
+ catch (UnsupportedEncodingException ex) {
+ // Should never happen...
}
}
-
- return true;
+ return false;
}
}
spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceWebHandler.java+30 26
@@ -28,7 +28,6 @@
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-import reactor.core.Exceptions;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.InitializingBean;
@@ -314,9 +313,9 @@ public Mono<Void> handle(ServerWebExchange exchange) {
}
protected Mono<Resource> getResource(ServerWebExchange exchange) {
-
String name = HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE;
PathContainer pathWithinHandler = exchange.getRequiredAttribute(name);
+
String path = processPath(pathWithinHandler.value());
if (!StringUtils.hasText(path) || isInvalidPath(path)) {
if (logger.isTraceEnabled()) {
@@ -324,31 +323,11 @@ protected Mono<Resource> getResource(ServerWebExchange exchange) {
}
return Mono.empty();
}
-
- if (path.contains("%")) {
- try {
- // Use URLDecoder (vs UriUtils) to preserve potentially decoded UTF-8 chars
- String decodedPath = URLDecoder.decode(path, "UTF-8");
- if (isInvalidPath(decodedPath)) {
- if (logger.isTraceEnabled()) {
- logger.trace("Ignoring invalid resource path with escape sequences [" + path + "].");
- }
- return Mono.empty();
- }
- decodedPath = processPath(decodedPath);
- if (isInvalidPath(decodedPath)) {
- if (logger.isTraceEnabled()) {
- logger.trace("Ignoring invalid resource path with escape sequences [" + path + "].");
- }
- return Mono.empty();
- }
- }
- catch (IllegalArgumentException ex) {
- // ignore
- }
- catch (UnsupportedEncodingException ex) {
- return Mono.error(Exceptions.propagate(ex));
+ if (isInvalidEncodedPath(path)) {
+ if (logger.isTraceEnabled()) {
+ logger.trace("Ignoring invalid resource path with escape sequences [" + path + "]");
}
+ return Mono.empty();
}
ResourceResolverChain resolveChain = createResolverChain();
@@ -420,6 +399,31 @@ else if (path.charAt(i) > ' ' && path.charAt(i) != 127) {
return (slash ? "/" : "");
}
+ /**
+ * Check whether the given path contains invalid escape sequences.
+ * @param path the path to validate
+ * @return {@code true} if the path is invalid, {@code false} otherwise
+ */
+ private boolean isInvalidEncodedPath(String path) {
+ if (path.contains("%")) {
+ try {
+ // Use URLDecoder (vs UriUtils) to preserve potentially decoded UTF-8 chars
+ String decodedPath = URLDecoder.decode(path, "UTF-8");
+ if (isInvalidPath(decodedPath)) {
+ return true;
+ }
+ decodedPath = processPath(decodedPath);
+ if (isInvalidPath(decodedPath)) {
+ return true;
+ }
+ }
+ catch (IllegalArgumentException | UnsupportedEncodingException ex) {
+ // Should never happen...
+ }
+ }
+ return false;
+ }
+
/**
* Identifies invalid resource paths. By default rejects:
* <ul>

Consistent trace logging in PathResourceResolver

Juergen Hoeller· Mar 29, 2018, 02:04 PM+1010695bf2961f
spring-webflux/src/main/java/org/springframework/web/reactive/resource/PathResourceResolver.java+5 5
@@ -118,10 +118,10 @@ protected Mono<Resource> getResource(String resourcePath, Resource location) {
}
else if (logger.isTraceEnabled()) {
Resource[] allowedLocations = getAllowedLocations();
- logger.trace("Resource path=\"" + resourcePath + "\" was successfully resolved " +
- "but resource=\"" + resource.getURL() + "\" is neither under the " +
- "current location=\"" + location.getURL() + "\" nor under any of the " +
- "allowed locations=" + (allowedLocations != null ? Arrays.asList(allowedLocations) : "[]"));
+ logger.trace("Resource path \"" + resourcePath + "\" was successfully resolved " +
+ "but resource \"" + resource.getURL() + "\" is neither under the " +
+ "current location \"" + location.getURL() + "\" nor under any of the " +
+ "allowed locations " + (allowedLocations != null ? Arrays.asList(allowedLocations) : "[]"));
}
}
else if (logger.isTraceEnabled()) {
@@ -195,7 +195,7 @@ private boolean isInvalidEncodedPath(String resourcePath) {
String decodedPath = URLDecoder.decode(resourcePath, "UTF-8");
if (decodedPath.contains("../") || decodedPath.contains("..\\")) {
if (logger.isTraceEnabled()) {
- logger.trace("Ignoring invalid resource path with escape sequences [" + resourcePath + "]");
+ logger.trace("Resolved resource path contains encoded \"../\" or \"..\\\": " + resourcePath);
}
return true;
}
spring-webmvc/src/main/java/org/springframework/web/servlet/resource/PathResourceResolver.java+5 5
@@ -189,10 +189,10 @@ protected Resource getResource(String resourcePath, Resource location) throws IO
}
else if (logger.isTraceEnabled()) {
Resource[] allowedLocations = getAllowedLocations();
- logger.trace("Resource path=\"" + resourcePath + "\" was successfully resolved " +
- "but resource=\"" + resource.getURL() + "\" is neither under the " +
- "current location=\"" + location.getURL() + "\" nor under any of the " +
- "allowed locations=" + (allowedLocations != null ? Arrays.asList(allowedLocations) : "[]"));
+ logger.trace("Resource path \"" + resourcePath + "\" was successfully resolved " +
+ "but resource \"" + resource.getURL() + "\" is neither under the " +
+ "current location \"" + location.getURL() + "\" nor under any of the " +
+ "allowed locations " + (allowedLocations != null ? Arrays.asList(allowedLocations) : "[]"));
}
}
return null;
@@ -286,7 +286,7 @@ private boolean isInvalidEncodedPath(String resourcePath) {
String decodedPath = URLDecoder.decode(resourcePath, "UTF-8");
if (decodedPath.contains("../") || decodedPath.contains("..\\")) {
if (logger.isTraceEnabled()) {
- logger.trace("Ignoring invalid resource path with escape sequences [" + resourcePath + "]");
+ logger.trace("Resolved resource path contains encoded \"../\" or \"..\\\": " + resourcePath);
}
return true;
}

References