Browse Source

Normalize static resource path early

Rather than leaving it to the Resource implementation, and
potentially normalizing twice, we apply it once as part of the
initial processPath checks.

Closes gh-33689
pull/33720/head
rstoyanchev 1 year ago
parent
commit
3bfbe30a78
  1. 23
      spring-webflux/src/main/java/org/springframework/web/reactive/function/server/PathResourceLookupFunction.java
  2. 20
      spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceWebHandler.java
  3. 2
      spring-webflux/src/test/java/org/springframework/web/reactive/resource/ResourceWebHandlerTests.java
  4. 20
      spring-webmvc/src/main/java/org/springframework/web/servlet/function/PathResourceLookupFunction.java
  5. 20
      spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java
  6. 1
      spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandlerTests.java

23
spring-webflux/src/main/java/org/springframework/web/reactive/function/server/PathResourceLookupFunction.java

@ -104,7 +104,8 @@ class PathResourceLookupFunction implements Function<ServerRequest, Mono<Resourc
protected String processPath(String path) { protected String processPath(String path) {
path = StringUtils.replace(path, "\\", "/"); path = StringUtils.replace(path, "\\", "/");
path = cleanDuplicateSlashes(path); path = cleanDuplicateSlashes(path);
return cleanLeadingSlash(path); path = cleanLeadingSlash(path);
return normalizePath(path);
} }
private String cleanDuplicateSlashes(String path) { private String cleanDuplicateSlashes(String path) {
@ -146,6 +147,21 @@ class PathResourceLookupFunction implements Function<ServerRequest, Mono<Resourc
return (slash ? "/" : ""); return (slash ? "/" : "");
} }
private static String normalizePath(String path) {
if (path.contains("%")) {
try {
path = URLDecoder.decode(path, StandardCharsets.UTF_8);
}
catch (Exception ex) {
return "";
}
if (path.contains("../")) {
path = StringUtils.cleanPath(path);
}
}
return path;
}
private boolean isInvalidPath(String path) { private boolean isInvalidPath(String path) {
if (path.contains("WEB-INF") || path.contains("META-INF")) { if (path.contains("WEB-INF") || path.contains("META-INF")) {
return true; return true;
@ -156,10 +172,7 @@ class PathResourceLookupFunction implements Function<ServerRequest, Mono<Resourc
return true; return true;
} }
} }
if (path.contains("..") && StringUtils.cleanPath(path).contains("../")) { return path.contains("../");
return true;
}
return false;
} }
/** /**

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

@ -523,7 +523,8 @@ public class ResourceWebHandler implements WebHandler, InitializingBean {
protected String processPath(String path) { protected String processPath(String path) {
path = StringUtils.replace(path, "\\", "/"); path = StringUtils.replace(path, "\\", "/");
path = cleanDuplicateSlashes(path); path = cleanDuplicateSlashes(path);
return cleanLeadingSlash(path); path = cleanLeadingSlash(path);
return normalizePath(path);
} }
private String cleanDuplicateSlashes(String path) { private String cleanDuplicateSlashes(String path) {
@ -565,6 +566,21 @@ public class ResourceWebHandler implements WebHandler, InitializingBean {
return (slash ? "/" : ""); return (slash ? "/" : "");
} }
private static String normalizePath(String path) {
if (path.contains("%")) {
try {
path = URLDecoder.decode(path, StandardCharsets.UTF_8);
}
catch (Exception ex) {
return "";
}
if (path.contains("../")) {
path = StringUtils.cleanPath(path);
}
}
return path;
}
/** /**
* Check whether the given path contains invalid escape sequences. * Check whether the given path contains invalid escape sequences.
* @param path the path to validate * @param path the path to validate
@ -623,7 +639,7 @@ public class ResourceWebHandler implements WebHandler, InitializingBean {
return true; return true;
} }
} }
if (path.contains("..") && StringUtils.cleanPath(path).contains("../")) { if (path.contains("../")) {
if (logger.isWarnEnabled()) { if (logger.isWarnEnabled()) {
logger.warn(LogFormatUtils.formatValue( logger.warn(LogFormatUtils.formatValue(
"Path contains \"../\" after call to StringUtils#cleanPath: [" + path + "]", -1, true)); "Path contains \"../\" after call to StringUtils#cleanPath: [" + path + "]", -1, true));

2
spring-webflux/src/test/java/org/springframework/web/reactive/resource/ResourceWebHandlerTests.java

@ -670,7 +670,6 @@ class ResourceWebHandlerTests {
testInvalidPath("/../.." + secretPath, handler); testInvalidPath("/../.." + secretPath, handler);
testInvalidPath("/%2E%2E/testsecret/secret.txt", handler); testInvalidPath("/%2E%2E/testsecret/secret.txt", 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) { private void testInvalidPath(String requestPath, ResourceWebHandler handler) {
@ -705,7 +704,6 @@ class ResourceWebHandlerTests {
testResolvePathWithTraversal(method, "/url:" + secretPath); testResolvePathWithTraversal(method, "/url:" + secretPath);
testResolvePathWithTraversal(method, "////../.." + secretPath); testResolvePathWithTraversal(method, "////../.." + secretPath);
testResolvePathWithTraversal(method, "/%2E%2E/testsecret/secret.txt"); testResolvePathWithTraversal(method, "/%2E%2E/testsecret/secret.txt");
testResolvePathWithTraversal(method, "%2F%2F%2E%2E%2F%2Ftestsecret/secret.txt");
testResolvePathWithTraversal(method, "url:" + secretPath); testResolvePathWithTraversal(method, "url:" + secretPath);
// The following tests fail with a MalformedURLException on Windows // The following tests fail with a MalformedURLException on Windows

20
spring-webmvc/src/main/java/org/springframework/web/servlet/function/PathResourceLookupFunction.java

@ -105,7 +105,8 @@ class PathResourceLookupFunction implements Function<ServerRequest, Optional<Res
protected String processPath(String path) { protected String processPath(String path) {
path = StringUtils.replace(path, "\\", "/"); path = StringUtils.replace(path, "\\", "/");
path = cleanDuplicateSlashes(path); path = cleanDuplicateSlashes(path);
return cleanLeadingSlash(path); path = cleanLeadingSlash(path);
return normalizePath(path);
} }
private String cleanDuplicateSlashes(String path) { private String cleanDuplicateSlashes(String path) {
@ -147,6 +148,21 @@ class PathResourceLookupFunction implements Function<ServerRequest, Optional<Res
return (slash ? "/" : ""); return (slash ? "/" : "");
} }
private static String normalizePath(String path) {
if (path.contains("%")) {
try {
path = URLDecoder.decode(path, StandardCharsets.UTF_8);
}
catch (Exception ex) {
return "";
}
if (path.contains("../")) {
path = StringUtils.cleanPath(path);
}
}
return path;
}
private boolean isInvalidPath(String path) { private boolean isInvalidPath(String path) {
if (path.contains("WEB-INF") || path.contains("META-INF")) { if (path.contains("WEB-INF") || path.contains("META-INF")) {
return true; return true;
@ -157,7 +173,7 @@ class PathResourceLookupFunction implements Function<ServerRequest, Optional<Res
return true; return true;
} }
} }
return path.contains("..") && StringUtils.cleanPath(path).contains("../"); return path.contains("../");
} }
private boolean isInvalidEncodedInputPath(String path) { private boolean isInvalidEncodedInputPath(String path) {

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

@ -682,7 +682,8 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
protected String processPath(String path) { protected String processPath(String path) {
path = StringUtils.replace(path, "\\", "/"); path = StringUtils.replace(path, "\\", "/");
path = cleanDuplicateSlashes(path); path = cleanDuplicateSlashes(path);
return cleanLeadingSlash(path); path = cleanLeadingSlash(path);
return normalizePath(path);
} }
private String cleanDuplicateSlashes(String path) { private String cleanDuplicateSlashes(String path) {
@ -724,6 +725,21 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
return (slash ? "/" : ""); return (slash ? "/" : "");
} }
private static String normalizePath(String path) {
if (path.contains("%")) {
try {
path = URLDecoder.decode(path, StandardCharsets.UTF_8);
}
catch (Exception ex) {
return "";
}
if (path.contains("../")) {
path = StringUtils.cleanPath(path);
}
}
return path;
}
/** /**
* Check whether the given path contains invalid escape sequences. * Check whether the given path contains invalid escape sequences.
* @param path the path to validate * @param path the path to validate
@ -783,7 +799,7 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
return true; return true;
} }
} }
if (path.contains("..") && StringUtils.cleanPath(path).contains("../")) { if (path.contains("../")) {
if (logger.isWarnEnabled()) { if (logger.isWarnEnabled()) {
logger.warn(LogFormatUtils.formatValue( logger.warn(LogFormatUtils.formatValue(
"Path contains \"../\" after call to StringUtils#cleanPath: [" + path + "]", -1, true)); "Path contains \"../\" after call to StringUtils#cleanPath: [" + path + "]", -1, true));

1
spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandlerTests.java

@ -656,7 +656,6 @@ class ResourceHttpRequestHandlerTests {
testInvalidPath("/../.." + secretPath); testInvalidPath("/../.." + secretPath);
testInvalidPath("/%2E%2E/testsecret/secret.txt"); testInvalidPath("/%2E%2E/testsecret/secret.txt");
testInvalidPath("/%2E%2E/testsecret/secret.txt"); testInvalidPath("/%2E%2E/testsecret/secret.txt");
testInvalidPath("%2F%2F%2E%2E%2F%2F%2E%2E" + secretPath);
} }
private void testInvalidPath(String requestPath) { private void testInvalidPath(String requestPath) {

Loading…
Cancel
Save