mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Warn against unsafe static resource locations
Prior to this commit, `ResourceHandlerUtils` would perform resource location checks to ensure that the configured location is valid. This commit also ensures that we log a WARN message if the application chooses a well-known unsafe location like "classpath:" or the root Servlet context for serving static resources. Closes gh-36692
This commit is contained in:
+17
-2
@@ -19,12 +19,14 @@ package org.springframework.web.reactive.resource;
|
||||
import java.io.IOException;
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.ContextResource;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.UrlResource;
|
||||
@@ -51,7 +53,8 @@ public abstract class ResourceHandlerUtils {
|
||||
|
||||
|
||||
/**
|
||||
* Assert the given location is not null, and its path ends on slash.
|
||||
* Assert the given location is valid.
|
||||
* Location should not be null, its path ends with a slash, and not be an unsafe location.
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
public static void assertResourceLocation(@Nullable Resource location) {
|
||||
@@ -66,6 +69,17 @@ public abstract class ResourceHandlerUtils {
|
||||
}
|
||||
else if (location instanceof ClassPathResource classPathResource) {
|
||||
path = classPathResource.getPath();
|
||||
if (path.isEmpty() || "/".equals(path)) {
|
||||
logger.warn("Resource location '" + location + "' is considered unsafe " +
|
||||
"and should not be used as it provides access to the entire classpath.");
|
||||
}
|
||||
}
|
||||
else if (location instanceof ContextResource contextResource) {
|
||||
path = contextResource.getPathWithinContext();
|
||||
if ("/".equals(path)) {
|
||||
logger.warn("Resource location '" + location + "' is considered unsafe " +
|
||||
"and should not be used as it provides access to the root servlet context.");
|
||||
}
|
||||
}
|
||||
else if (location instanceof UrlResource) {
|
||||
path = location.getURL().toExternalForm();
|
||||
@@ -175,7 +189,8 @@ public abstract class ResourceHandlerUtils {
|
||||
* @return {@code true} if the path is invalid, {@code false} otherwise
|
||||
*/
|
||||
public static boolean isInvalidPath(String path) {
|
||||
if (path.contains("WEB-INF") || path.contains("META-INF")) {
|
||||
String pathLowerCase = path.toLowerCase(Locale.ROOT);
|
||||
if (pathLowerCase.contains("web-inf") || pathLowerCase.contains("meta-inf")) {
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn(LogFormatUtils.formatValue(
|
||||
"Path with \"WEB-INF\" or \"META-INF\": [" + path + "]", -1, true));
|
||||
|
||||
+5
-4
@@ -153,10 +153,7 @@ public class ResourceWebHandler implements WebHandler, InitializingBean {
|
||||
public void setLocations(@Nullable List<Resource> locations) {
|
||||
this.locationResources.clear();
|
||||
if (locations != null) {
|
||||
for (Resource location : locations) {
|
||||
ResourceHandlerUtils.assertResourceLocation(location);
|
||||
this.locationResources.add(location);
|
||||
}
|
||||
this.locationResources.addAll(locations);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -373,6 +370,10 @@ public class ResourceWebHandler implements WebHandler, InitializingBean {
|
||||
}
|
||||
}
|
||||
|
||||
for (Resource location : result) {
|
||||
ResourceHandlerUtils.assertResourceLocation(location);
|
||||
}
|
||||
|
||||
if (isOptimizeLocations()) {
|
||||
result = result.stream().filter(Resource::exists).toList();
|
||||
}
|
||||
|
||||
+17
-2
@@ -19,12 +19,14 @@ package org.springframework.web.servlet.resource;
|
||||
import java.io.IOException;
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.ContextResource;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.UrlResource;
|
||||
@@ -52,7 +54,8 @@ public abstract class ResourceHandlerUtils {
|
||||
|
||||
|
||||
/**
|
||||
* Assert the given location is not null, and its path ends on slash.
|
||||
* Assert the given location is valid.
|
||||
* Location should not be null, its path ends with a slash, and not be an unsafe location.
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
public static void assertResourceLocation(@Nullable Resource location) {
|
||||
@@ -67,6 +70,17 @@ public abstract class ResourceHandlerUtils {
|
||||
}
|
||||
else if (location instanceof ClassPathResource classPathResource) {
|
||||
path = classPathResource.getPath();
|
||||
if (path.isEmpty() || "/".equals(path)) {
|
||||
logger.warn("Resource location '" + location + "' is considered unsafe " +
|
||||
"and should not be used as it provides access to the entire classpath.");
|
||||
}
|
||||
}
|
||||
else if (location instanceof ContextResource contextResource) {
|
||||
path = contextResource.getPathWithinContext();
|
||||
if ("/".equals(path)) {
|
||||
logger.warn("Resource location '" + location + "' is considered unsafe " +
|
||||
"and should not be used as it provides access to the root servlet context.");
|
||||
}
|
||||
}
|
||||
else if (location instanceof UrlResource) {
|
||||
path = location.getURL().toExternalForm();
|
||||
@@ -176,7 +190,8 @@ public abstract class ResourceHandlerUtils {
|
||||
* @return {@code true} if the path is invalid, {@code false} otherwise
|
||||
*/
|
||||
public static boolean isInvalidPath(String path) {
|
||||
if (path.contains("WEB-INF") || path.contains("META-INF")) {
|
||||
String pathLowerCase = path.toLowerCase(Locale.ROOT);
|
||||
if (pathLowerCase.contains("web-inf") || pathLowerCase.contains("meta-inf")) {
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn(LogFormatUtils.formatValue(
|
||||
"Path with \"WEB-INF\" or \"META-INF\": [" + path + "]", -1, true));
|
||||
|
||||
+5
-4
@@ -170,10 +170,7 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
|
||||
public void setLocations(List<Resource> locations) {
|
||||
Assert.notNull(locations, "Locations list must not be null");
|
||||
this.locationResources.clear();
|
||||
for (Resource location : locations) {
|
||||
ResourceHandlerUtils.assertResourceLocation(location);
|
||||
this.locationResources.add(location);
|
||||
}
|
||||
this.locationResources.addAll(locations);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -471,6 +468,10 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
|
||||
}
|
||||
|
||||
result.addAll(this.locationResources);
|
||||
for (Resource location : result) {
|
||||
ResourceHandlerUtils.assertResourceLocation(location);
|
||||
}
|
||||
|
||||
if (isOptimizeLocations()) {
|
||||
result = result.stream().filter(Resource::exists).toList();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user