Skip to content

Commit 0725bf4

Browse files
committed
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
1 parent 8965d9b commit 0725bf4

4 files changed

Lines changed: 44 additions & 12 deletions

File tree

spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceHandlerUtils.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,14 @@
1919
import java.io.IOException;
2020
import java.net.URLDecoder;
2121
import java.nio.charset.StandardCharsets;
22+
import java.util.Locale;
2223

2324
import org.apache.commons.logging.Log;
2425
import org.apache.commons.logging.LogFactory;
2526
import org.jspecify.annotations.Nullable;
2627

2728
import org.springframework.core.io.ClassPathResource;
29+
import org.springframework.core.io.ContextResource;
2830
import org.springframework.core.io.FileSystemResource;
2931
import org.springframework.core.io.Resource;
3032
import org.springframework.core.io.UrlResource;
@@ -51,7 +53,8 @@ public abstract class ResourceHandlerUtils {
5153

5254

5355
/**
54-
* Assert the given location is not null, and its path ends on slash.
56+
* Assert the given location is valid.
57+
* Location should not be null, its path ends with a slash, and not be an unsafe location.
5558
*/
5659
@SuppressWarnings("removal")
5760
public static void assertResourceLocation(@Nullable Resource location) {
@@ -66,6 +69,17 @@ else if (location instanceof FileSystemResource fileSystemResource) {
6669
}
6770
else if (location instanceof ClassPathResource classPathResource) {
6871
path = classPathResource.getPath();
72+
if (path.isEmpty() || "/".equals(path)) {
73+
logger.warn("Resource location '" + location + "' is considered unsafe " +
74+
"and should not be used as it provides access to the entire classpath.");
75+
}
76+
}
77+
else if (location instanceof ContextResource contextResource) {
78+
path = contextResource.getPathWithinContext();
79+
if ("/".equals(path)) {
80+
logger.warn("Resource location '" + location + "' is considered unsafe " +
81+
"and should not be used as it provides access to the root servlet context.");
82+
}
6983
}
7084
else if (location instanceof UrlResource) {
7185
path = location.getURL().toExternalForm();
@@ -175,7 +189,8 @@ public static boolean shouldIgnoreInputPath(String path) {
175189
* @return {@code true} if the path is invalid, {@code false} otherwise
176190
*/
177191
public static boolean isInvalidPath(String path) {
178-
if (path.contains("WEB-INF") || path.contains("META-INF")) {
192+
String pathLowerCase = path.toLowerCase(Locale.ROOT);
193+
if (pathLowerCase.contains("web-inf") || pathLowerCase.contains("meta-inf")) {
179194
if (logger.isWarnEnabled()) {
180195
logger.warn(LogFormatUtils.formatValue(
181196
"Path with \"WEB-INF\" or \"META-INF\": [" + path + "]", -1, true));

spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceWebHandler.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -153,10 +153,7 @@ public List<String> getLocationValues() {
153153
public void setLocations(@Nullable List<Resource> locations) {
154154
this.locationResources.clear();
155155
if (locations != null) {
156-
for (Resource location : locations) {
157-
ResourceHandlerUtils.assertResourceLocation(location);
158-
this.locationResources.add(location);
159-
}
156+
this.locationResources.addAll(locations);
160157
}
161158
}
162159

@@ -373,6 +370,10 @@ private void resolveResourceLocations() {
373370
}
374371
}
375372

373+
for (Resource location : result) {
374+
ResourceHandlerUtils.assertResourceLocation(location);
375+
}
376+
376377
if (isOptimizeLocations()) {
377378
result = result.stream().filter(Resource::exists).toList();
378379
}

spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHandlerUtils.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,14 @@
1919
import java.io.IOException;
2020
import java.net.URLDecoder;
2121
import java.nio.charset.StandardCharsets;
22+
import java.util.Locale;
2223

2324
import org.apache.commons.logging.Log;
2425
import org.apache.commons.logging.LogFactory;
2526
import org.jspecify.annotations.Nullable;
2627

2728
import org.springframework.core.io.ClassPathResource;
29+
import org.springframework.core.io.ContextResource;
2830
import org.springframework.core.io.FileSystemResource;
2931
import org.springframework.core.io.Resource;
3032
import org.springframework.core.io.UrlResource;
@@ -52,7 +54,8 @@ public abstract class ResourceHandlerUtils {
5254

5355

5456
/**
55-
* Assert the given location is not null, and its path ends on slash.
57+
* Assert the given location is valid.
58+
* Location should not be null, its path ends with a slash, and not be an unsafe location.
5659
*/
5760
@SuppressWarnings("removal")
5861
public static void assertResourceLocation(@Nullable Resource location) {
@@ -67,6 +70,17 @@ else if (location instanceof FileSystemResource fileSystemResource) {
6770
}
6871
else if (location instanceof ClassPathResource classPathResource) {
6972
path = classPathResource.getPath();
73+
if (path.isEmpty() || "/".equals(path)) {
74+
logger.warn("Resource location '" + location + "' is considered unsafe " +
75+
"and should not be used as it provides access to the entire classpath.");
76+
}
77+
}
78+
else if (location instanceof ContextResource contextResource) {
79+
path = contextResource.getPathWithinContext();
80+
if ("/".equals(path)) {
81+
logger.warn("Resource location '" + location + "' is considered unsafe " +
82+
"and should not be used as it provides access to the root servlet context.");
83+
}
7084
}
7185
else if (location instanceof UrlResource) {
7286
path = location.getURL().toExternalForm();
@@ -176,7 +190,8 @@ public static boolean shouldIgnoreInputPath(String path) {
176190
* @return {@code true} if the path is invalid, {@code false} otherwise
177191
*/
178192
public static boolean isInvalidPath(String path) {
179-
if (path.contains("WEB-INF") || path.contains("META-INF")) {
193+
String pathLowerCase = path.toLowerCase(Locale.ROOT);
194+
if (pathLowerCase.contains("web-inf") || pathLowerCase.contains("meta-inf")) {
180195
if (logger.isWarnEnabled()) {
181196
logger.warn(LogFormatUtils.formatValue(
182197
"Path with \"WEB-INF\" or \"META-INF\": [" + path + "]", -1, true));

spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -170,10 +170,7 @@ public void setLocationValues(List<String> locations) {
170170
public void setLocations(List<Resource> locations) {
171171
Assert.notNull(locations, "Locations list must not be null");
172172
this.locationResources.clear();
173-
for (Resource location : locations) {
174-
ResourceHandlerUtils.assertResourceLocation(location);
175-
this.locationResources.add(location);
176-
}
173+
this.locationResources.addAll(locations);
177174
}
178175

179176
/**
@@ -471,6 +468,10 @@ private void resolveResourceLocations() {
471468
}
472469

473470
result.addAll(this.locationResources);
471+
for (Resource location : result) {
472+
ResourceHandlerUtils.assertResourceLocation(location);
473+
}
474+
474475
if (isOptimizeLocations()) {
475476
result = result.stream().filter(Resource::exists).toList();
476477
}

0 commit comments

Comments
 (0)