Verifies resources are in allowed locations
This commit is contained in:
@@ -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<Resource> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Resource> 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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user