Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #6979 from eclipse/jetty-9.4.x-6497-AliasCheckers
Backport #6681 alias checker changes to Jetty 9.4
- Loading branch information
Showing
18 changed files
with
580 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
210 changes: 210 additions & 0 deletions
210
jetty-server/src/main/java/org/eclipse/jetty/server/AllowedResourceAliasChecker.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,210 @@ | ||
// | ||
// ======================================================================== | ||
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others. | ||
// ------------------------------------------------------------------------ | ||
// All rights reserved. This program and the accompanying materials | ||
// are made available under the terms of the Eclipse Public License v1.0 | ||
// and Apache License v2.0 which accompanies this distribution. | ||
// | ||
// The Eclipse Public License is available at | ||
// http://www.eclipse.org/legal/epl-v10.html | ||
// | ||
// The Apache License v2.0 is available at | ||
// http://www.opensource.org/licenses/apache2.0.php | ||
// | ||
// You may elect to redistribute this code under either of these licenses. | ||
// ======================================================================== | ||
// | ||
|
||
package org.eclipse.jetty.server; | ||
|
||
import java.io.IOException; | ||
import java.nio.file.Files; | ||
import java.nio.file.LinkOption; | ||
import java.nio.file.Path; | ||
import java.nio.file.Paths; | ||
import java.util.ArrayList; | ||
import java.util.Arrays; | ||
import java.util.List; | ||
import java.util.Objects; | ||
|
||
import org.eclipse.jetty.server.handler.ContextHandler; | ||
import org.eclipse.jetty.util.component.AbstractLifeCycle; | ||
import org.eclipse.jetty.util.log.Log; | ||
import org.eclipse.jetty.util.log.Logger; | ||
import org.eclipse.jetty.util.resource.PathResource; | ||
import org.eclipse.jetty.util.resource.Resource; | ||
|
||
/** | ||
* <p>This will approve any alias to anything inside of the {@link ContextHandler}s resource base which | ||
* is not protected by a protected target as defined by {@link ContextHandler#getProtectedTargets()} at start.</p> | ||
* <p>Aliases approved by this may still be able to bypass SecurityConstraints, so this class would need to be extended | ||
* to enforce any additional security constraints that are required.</p> | ||
*/ | ||
public class AllowedResourceAliasChecker extends AbstractLifeCycle implements ContextHandler.AliasCheck | ||
{ | ||
private static final Logger LOG = Log.getLogger(AllowedResourceAliasChecker.class); | ||
protected static final LinkOption[] FOLLOW_LINKS = new LinkOption[0]; | ||
protected static final LinkOption[] NO_FOLLOW_LINKS = new LinkOption[]{LinkOption.NOFOLLOW_LINKS}; | ||
|
||
private final ContextHandler _contextHandler; | ||
private final List<Path> _protected = new ArrayList<>(); | ||
protected Path _base; | ||
|
||
/** | ||
* @param contextHandler the context handler to use. | ||
*/ | ||
public AllowedResourceAliasChecker(ContextHandler contextHandler) | ||
{ | ||
_contextHandler = contextHandler; | ||
} | ||
|
||
protected ContextHandler getContextHandler() | ||
{ | ||
return _contextHandler; | ||
} | ||
|
||
@Override | ||
protected void doStart() throws Exception | ||
{ | ||
_base = getPath(_contextHandler.getBaseResource()); | ||
if (_base == null) | ||
_base = Paths.get("/").toAbsolutePath(); | ||
if (Files.exists(_base, NO_FOLLOW_LINKS)) | ||
_base = _base.toRealPath(FOLLOW_LINKS); | ||
|
||
String[] protectedTargets = _contextHandler.getProtectedTargets(); | ||
if (protectedTargets != null) | ||
{ | ||
for (String s : protectedTargets) | ||
_protected.add(_base.getFileSystem().getPath(_base.toString(), s)); | ||
} | ||
} | ||
|
||
@Override | ||
protected void doStop() throws Exception | ||
{ | ||
_base = null; | ||
_protected.clear(); | ||
} | ||
|
||
@Override | ||
public boolean check(String pathInContext, Resource resource) | ||
{ | ||
try | ||
{ | ||
// The existence check resolves the symlinks. | ||
if (!resource.exists()) | ||
return false; | ||
|
||
Path path = getPath(resource); | ||
if (path == null) | ||
return false; | ||
|
||
return check(pathInContext, path); | ||
} | ||
catch (Throwable t) | ||
{ | ||
if (LOG.isDebugEnabled()) | ||
LOG.debug("Failed to check alias", t); | ||
return false; | ||
} | ||
} | ||
|
||
protected boolean check(String pathInContext, Path path) | ||
{ | ||
// Allow any aliases (symlinks, 8.3, casing, etc.) so long as | ||
// the resulting real file is allowed. | ||
return isAllowed(getRealPath(path)); | ||
} | ||
|
||
protected boolean isAllowed(Path path) | ||
{ | ||
// If the resource doesn't exist we cannot determine whether it is protected so we assume it is. | ||
if (path != null && Files.exists(path)) | ||
{ | ||
// Walk the path parent links looking for the base resource, but failing if any steps are protected | ||
while (path != null) | ||
{ | ||
// If the path is the same file as the base, then it is contained in the base and | ||
// is not protected. | ||
if (isSameFile(path, _base)) | ||
return true; | ||
|
||
// If the path is the same file as any protected resources, then it is protected. | ||
for (Path p : _protected) | ||
{ | ||
if (isSameFile(path, p)) | ||
return false; | ||
} | ||
|
||
// Walks up the aliased path name, not the real path name. | ||
// If WEB-INF is a link to /var/lib/webmeta then after checking | ||
// a URI of /WEB-INF/file.xml the parent is /WEB-INF and not /var/lib/webmeta | ||
path = path.getParent(); | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
|
||
protected boolean isSameFile(Path path1, Path path2) | ||
{ | ||
if (Objects.equals(path1, path2)) | ||
return true; | ||
try | ||
{ | ||
if (Files.isSameFile(path1, path2)) | ||
return true; | ||
} | ||
catch (Throwable t) | ||
{ | ||
if (LOG.isDebugEnabled()) | ||
LOG.debug("ignored", t); | ||
} | ||
return false; | ||
} | ||
|
||
private static Path getRealPath(Path path) | ||
{ | ||
if (path == null || !Files.exists(path)) | ||
return null; | ||
try | ||
{ | ||
path = path.toRealPath(FOLLOW_LINKS); | ||
if (Files.exists(path)) | ||
return path; | ||
} | ||
catch (IOException e) | ||
{ | ||
if (LOG.isDebugEnabled()) | ||
LOG.debug("No real path for {}", path, e); | ||
} | ||
return null; | ||
} | ||
|
||
protected Path getPath(Resource resource) | ||
{ | ||
try | ||
{ | ||
if (resource instanceof PathResource) | ||
return ((PathResource)resource).getPath(); | ||
return resource.getFile().toPath(); | ||
} | ||
catch (Throwable t) | ||
{ | ||
LOG.ignore(t); | ||
return null; | ||
} | ||
} | ||
|
||
@Override | ||
public String toString() | ||
{ | ||
return String.format("%s@%x{base=%s,protected=%s}", | ||
this.getClass().getSimpleName(), | ||
hashCode(), | ||
_base, | ||
Arrays.asList(_contextHandler.getProtectedTargets())); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
91 changes: 91 additions & 0 deletions
91
jetty-server/src/main/java/org/eclipse/jetty/server/SymlinkAllowedResourceAliasChecker.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
// | ||
// ======================================================================== | ||
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others. | ||
// ------------------------------------------------------------------------ | ||
// All rights reserved. This program and the accompanying materials | ||
// are made available under the terms of the Eclipse Public License v1.0 | ||
// and Apache License v2.0 which accompanies this distribution. | ||
// | ||
// The Eclipse Public License is available at | ||
// http://www.eclipse.org/legal/epl-v10.html | ||
// | ||
// The Apache License v2.0 is available at | ||
// http://www.opensource.org/licenses/apache2.0.php | ||
// | ||
// You may elect to redistribute this code under either of these licenses. | ||
// ======================================================================== | ||
// | ||
|
||
package org.eclipse.jetty.server; | ||
|
||
import java.io.File; | ||
import java.nio.file.Files; | ||
import java.nio.file.Path; | ||
|
||
import org.eclipse.jetty.server.handler.ContextHandler; | ||
import org.eclipse.jetty.util.log.Log; | ||
import org.eclipse.jetty.util.log.Logger; | ||
|
||
/** | ||
* An extension of {@link AllowedResourceAliasChecker} which will allow symlinks alias to arbitrary | ||
* targets, so long as the symlink file itself is an allowed resource. | ||
*/ | ||
public class SymlinkAllowedResourceAliasChecker extends AllowedResourceAliasChecker | ||
{ | ||
private static final Logger LOG = Log.getLogger(SymlinkAllowedResourceAliasChecker.class); | ||
|
||
/** | ||
* @param contextHandler the context handler to use. | ||
*/ | ||
public SymlinkAllowedResourceAliasChecker(ContextHandler contextHandler) | ||
{ | ||
super(contextHandler); | ||
} | ||
|
||
@Override | ||
protected boolean check(String pathInContext, Path path) | ||
{ | ||
// do not allow any file separation characters in the URI, as we need to know exactly what are the segments | ||
if (File.separatorChar != '/' && pathInContext.indexOf(File.separatorChar) >= 0) | ||
return false; | ||
|
||
// Split the URI path into segments, to walk down the resource tree and build the realURI of any symlink found | ||
// We rebuild the realURI, segment by segment, getting the real name at each step, so that we can distinguish between | ||
// alias types. Specifically, so we can allow a symbolic link so long as it's realpath is not protected. | ||
String[] segments = pathInContext.substring(1).split("/"); | ||
Path fromBase = _base; | ||
StringBuilder realURI = new StringBuilder(); | ||
|
||
try | ||
{ | ||
for (String segment : segments) | ||
{ | ||
// Add the segment to the path and realURI. | ||
fromBase = fromBase.resolve(segment); | ||
realURI.append("/").append(fromBase.toRealPath(NO_FOLLOW_LINKS).getFileName()); | ||
|
||
// If the ancestor of the alias is a symlink, then check if the real URI is protected, otherwise allow. | ||
// This allows symlinks like /other->/WEB-INF and /external->/var/lib/docroot | ||
// This does not allow symlinks like /WeB-InF->/var/lib/other | ||
if (Files.isSymbolicLink(fromBase)) | ||
return !getContextHandler().isProtectedTarget(realURI.toString()); | ||
|
||
// If the ancestor is not allowed then do not allow. | ||
if (!isAllowed(fromBase)) | ||
return false; | ||
|
||
// TODO as we are building the realURI of the resource, it would be possible to | ||
// re-check that against security constraints. | ||
} | ||
} | ||
catch (Throwable t) | ||
{ | ||
if (LOG.isDebugEnabled()) | ||
LOG.debug("Failed to check alias", t); | ||
return false; | ||
} | ||
|
||
// No symlink found, so must be allowed. Double check it is the right path we checked. | ||
return isSameFile(fromBase, path); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.