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
This commit is contained in:
rstoyanchev 2024-10-14 12:55:18 +01:00
parent 32962894a0
commit 3bfbe30a78
6 changed files with 72 additions and 14 deletions

View File

@ -104,7 +104,8 @@ class PathResourceLookupFunction implements Function<ServerRequest, Mono<Resourc
protected String processPath(String path) {
path = StringUtils.replace(path, "\\", "/");
path = cleanDuplicateSlashes(path);
return cleanLeadingSlash(path);
path = cleanLeadingSlash(path);
return normalizePath(path);
}
private String cleanDuplicateSlashes(String path) {
@ -146,6 +147,21 @@ class PathResourceLookupFunction implements Function<ServerRequest, Mono<Resourc
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) {
if (path.contains("WEB-INF") || path.contains("META-INF")) {
return true;
@ -156,10 +172,7 @@ class PathResourceLookupFunction implements Function<ServerRequest, Mono<Resourc
return true;
}
}
if (path.contains("..") && StringUtils.cleanPath(path).contains("../")) {
return true;
}
return false;
return path.contains("../");
}
/**

View File

@ -523,7 +523,8 @@ public class ResourceWebHandler implements WebHandler, InitializingBean {
protected String processPath(String path) {
path = StringUtils.replace(path, "\\", "/");
path = cleanDuplicateSlashes(path);
return cleanLeadingSlash(path);
path = cleanLeadingSlash(path);
return normalizePath(path);
}
private String cleanDuplicateSlashes(String path) {
@ -565,6 +566,21 @@ public class ResourceWebHandler implements WebHandler, InitializingBean {
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.
* @param path the path to validate
@ -623,7 +639,7 @@ public class ResourceWebHandler implements WebHandler, InitializingBean {
return true;
}
}
if (path.contains("..") && StringUtils.cleanPath(path).contains("../")) {
if (path.contains("../")) {
if (logger.isWarnEnabled()) {
logger.warn(LogFormatUtils.formatValue(
"Path contains \"../\" after call to StringUtils#cleanPath: [" + path + "]", -1, true));

View File

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

View File

@ -105,7 +105,8 @@ class PathResourceLookupFunction implements Function<ServerRequest, Optional<Res
protected String processPath(String path) {
path = StringUtils.replace(path, "\\", "/");
path = cleanDuplicateSlashes(path);
return cleanLeadingSlash(path);
path = cleanLeadingSlash(path);
return normalizePath(path);
}
private String cleanDuplicateSlashes(String path) {
@ -147,6 +148,21 @@ class PathResourceLookupFunction implements Function<ServerRequest, Optional<Res
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) {
if (path.contains("WEB-INF") || path.contains("META-INF")) {
return true;
@ -157,7 +173,7 @@ class PathResourceLookupFunction implements Function<ServerRequest, Optional<Res
return true;
}
}
return path.contains("..") && StringUtils.cleanPath(path).contains("../");
return path.contains("../");
}
private boolean isInvalidEncodedInputPath(String path) {

View File

@ -682,7 +682,8 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
protected String processPath(String path) {
path = StringUtils.replace(path, "\\", "/");
path = cleanDuplicateSlashes(path);
return cleanLeadingSlash(path);
path = cleanLeadingSlash(path);
return normalizePath(path);
}
private String cleanDuplicateSlashes(String path) {
@ -724,6 +725,21 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
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.
* @param path the path to validate
@ -783,7 +799,7 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
return true;
}
}
if (path.contains("..") && StringUtils.cleanPath(path).contains("../")) {
if (path.contains("../")) {
if (logger.isWarnEnabled()) {
logger.warn(LogFormatUtils.formatValue(
"Path contains \"../\" after call to StringUtils#cleanPath: [" + path + "]", -1, true));

View File

@ -656,7 +656,6 @@ class ResourceHttpRequestHandlerTests {
testInvalidPath("/../.." + secretPath);
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) {