Verifies resources are in allowed locations

This commit is contained in:
spencergibb
2020-05-26 15:28:18 -04:00
parent 2428c021b3
commit f470b52f1b
3 changed files with 96 additions and 8 deletions

View File

@@ -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;
}
}

View 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));
}
}

View File

@@ -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");