From be8c6acf653b9b50f841688b07ba5e2e9da70d2e Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Tue, 13 Jul 2021 01:09:58 +1000 Subject: [PATCH] Issue #6497 - AllowedResourceAliasChecker can test target of symlink Signed-off-by: Lachlan Roberts --- .../server/AllowedResourceAliasChecker.java | 86 +++++++++++++++---- 1 file changed, 68 insertions(+), 18 deletions(-) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AllowedResourceAliasChecker.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AllowedResourceAliasChecker.java index 0315b008e4b7..acfcd6c38448 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/AllowedResourceAliasChecker.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AllowedResourceAliasChecker.java @@ -18,14 +18,18 @@ package org.eclipse.jetty.server; -import java.io.File; - import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.resource.Resource; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.nio.file.Path; + /** * This will approve an alias to any resource which is not a protected target. * Except symlinks... @@ -34,10 +38,17 @@ public class AllowedResourceAliasChecker implements ContextHandler.AliasCheck { private static final Logger LOG = Log.getLogger(AllowedResourceAliasChecker.class); private final ContextHandler _contextHandler; + private final boolean _checkSymlinkTargets; public AllowedResourceAliasChecker(ContextHandler contextHandler) + { + this(contextHandler, false); + } + + public AllowedResourceAliasChecker(ContextHandler contextHandler, boolean checkSymlinkTargets) { _contextHandler = contextHandler; + _checkSymlinkTargets = checkSymlinkTargets; } @Override @@ -45,24 +56,19 @@ public boolean check(String uri, Resource resource) { try { - String baseResourcePath = _contextHandler.getBaseResource().getFile().getCanonicalPath(); - String resourcePath = resource.getFile().getCanonicalPath(); - if (!resourcePath.startsWith(baseResourcePath)) + if (!resource.exists()) return false; - for (String s : _contextHandler.getProtectedTargets()) + Path resourcePath = resource.getFile().toPath(); + Path realPath = resourcePath.toRealPath(LinkOption.NOFOLLOW_LINKS); + if (isProtectedPath(realPath, false)) + return false; + + if (_checkSymlinkTargets && hasSymbolicLink(resourcePath)) { - String protectedTarget = new File(_contextHandler.getBaseResource().getFile(), s).getCanonicalPath(); - if (StringUtil.startsWithIgnoreCase(resourcePath, protectedTarget)) - { - if (resourcePath.length() == protectedTarget.length()) - return false; - - // Check that the target prefix really is a path segment. - char c = resourcePath.charAt(protectedTarget.length()); - if (c == File.separatorChar) - return false; - } + realPath = resourcePath.toRealPath(); + if (isProtectedPath(realPath, true)) + return false; } } catch (Throwable t) @@ -71,7 +77,51 @@ public boolean check(String uri, Resource resource) return false; } - // TODO: Check symlink targets if flag is set. return true; } + + private boolean isProtectedPath(Path path, boolean followLinks) throws IOException + { + String basePath = followLinks ? _contextHandler.getBaseResource().getFile().toPath().toRealPath().toString() : + _contextHandler.getBaseResource().getFile().toPath().toRealPath(LinkOption.NOFOLLOW_LINKS).toString(); + String targetPath = path.toString(); + + if (!targetPath.startsWith(basePath)) + return true; + + for (String s : _contextHandler.getProtectedTargets()) + { + String protectedTarget = new File(basePath, s).getCanonicalPath(); + if (StringUtil.startsWithIgnoreCase(targetPath, protectedTarget)) + { + if (targetPath.length() == protectedTarget.length()) + return true; + + // Check that the target prefix really is a path segment. + char c = targetPath.charAt(protectedTarget.length()); + if (c == File.separatorChar) + return true; + } + } + + return false; + } + + private boolean hasSymbolicLink(Path path) + { + // Is file itself a symlink? + if (Files.isSymbolicLink(path)) + return true; + + // Lets try each path segment + Path base = path.getRoot(); + for (Path segment : path) + { + base = base.resolve(segment); + if (Files.isSymbolicLink(base)) + return true; + } + + return false; + } } \ No newline at end of file