diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/resource/GenericResourceRepository.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/resource/GenericResourceRepository.java index 87b89764..667827fc 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/resource/GenericResourceRepository.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/resource/GenericResourceRepository.java @@ -17,6 +17,7 @@ package org.springframework.cloud.config.server.resource; import java.io.IOException; +import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashSet; import java.util.Set; @@ -56,18 +57,22 @@ public class GenericResourceRepository if (StringUtils.hasText(path)) { String[] locations = this.service.getLocations(application, profile, label) .getLocations(); + ArrayList locationResources = new ArrayList<>(); + for (int i = locations.length; i-- > 0;) { + String location = locations[i]; + if (!PathUtils.isInvalidEncodedLocation(location)) { + locationResources.add(this.resourceLoader.getResource(location)); + } + } + try { - for (int i = locations.length; i-- > 0;) { - String location = locations[i]; - if (PathUtils.isInvalidEncodedLocation(location)) { - continue; - } + for (Resource location : locationResources) { for (String local : getProfilePaths(profile, path)) { if (!PathUtils.isInvalidPath(local) && !PathUtils.isInvalidEncodedPath(local)) { - Resource file = this.resourceLoader.getResource(location) - .createRelative(local); - if (file.exists() && file.isReadable()) { + Resource file = location.createRelative(local); + if (file.exists() && file.isReadable() && PathUtils + .checkResource(file, location, locationResources)) { return file; } } diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/support/PathUtils.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/support/PathUtils.java index e6bb0f99..3102aad3 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/support/PathUtils.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/support/PathUtils.java @@ -16,12 +16,17 @@ package org.springframework.cloud.config.server.support; +import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; +import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.core.io.UrlResource; import org.springframework.util.ResourceUtils; import org.springframework.util.StringUtils; @@ -205,4 +210,71 @@ public abstract class PathUtils { return false; } + /** + * Perform additional checks on a resolved resource beyond checking whether the + * resources exists and is readable. The default implementation also verifies the + * resource is either under the location relative to which it was found or is under + * one of the {@link #setAllowedLocations allowed locations}. + * @param resource the resource to check + * @param location the location relative to which the resource was found + * @param allowedLocations set of allowed locations + * @return "true" if resource is in a valid location, "false" otherwise. + * @throws IOException if Resource URLS fail to parse. + * @since 4.1.2 + */ + public static boolean checkResource(Resource resource, Resource location, + List allowedLocations) throws IOException { + if (isResourceUnderLocation(resource, location)) { + return true; + } + if (allowedLocations != null) { + for (Resource current : allowedLocations) { + if (isResourceUnderLocation(resource, current)) { + return true; + } + } + } + if (logger.isWarnEnabled()) { + logger.warn("Resource path \"" + location.getURI() + + "\" was successfully resolved " + "but resource \"" + + resource.getURL() + "\" is neither under the " + + "current location \"" + location.getURL() + + "\" nor under any of the " + "allowed locations " + + (allowedLocations != null ? allowedLocations : "[]")); + } + return false; + } + + private static boolean isResourceUnderLocation(Resource resource, Resource location) + throws IOException { + if (resource.getClass() != location.getClass()) { + return false; + } + + String resourcePath; + String locationPath; + + if (resource instanceof UrlResource) { + resourcePath = resource.getURL().toExternalForm(); + locationPath = StringUtils.cleanPath(location.getURL().toString()); + } + else if (resource instanceof ClassPathResource) { + resourcePath = ((ClassPathResource) resource).getPath(); + locationPath = StringUtils + .cleanPath(((ClassPathResource) location).getPath()); + } + else { + resourcePath = resource.getURL().getPath(); + locationPath = StringUtils.cleanPath(location.getURL().getPath()); + } + + if (locationPath.equals(resourcePath)) { + return true; + } + locationPath = (locationPath.endsWith("/") || locationPath.isEmpty() + ? locationPath : locationPath + "/"); + return (resourcePath.startsWith(locationPath) + && !isInvalidEncodedPath(resourcePath)); + } + } diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/resource/GenericResourceRepositoryTests.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/resource/GenericResourceRepositoryTests.java index f333c0d7..062a2c36 100644 --- a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/resource/GenericResourceRepositoryTests.java +++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/resource/GenericResourceRepositoryTests.java @@ -120,6 +120,17 @@ public class GenericResourceRepositoryTests { testInvalidPath("%2E%2E%2F"); } + @Test + public void invalidPathEncodedSlash() { + String file = System.getProperty("user.dir"); + file = file.replaceFirst("\\/", "%2f"); + file += "/src/test/resources/ssh/key"; + this.exception.expect(NoSuchResourceException.class); + this.nativeRepository.setSearchLocations("file:./"); + this.output.expect(containsString("is neither under the current location")); + this.repository.findOne("blah", "local", "master", file); + } + private void testInvalidPath(String label) { this.exception.expect(NoSuchResourceException.class); this.nativeRepository.setSearchLocations("file:./src/test/resources/test/local");