New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Issue #6497 - Replace the Alias checkers with new implementation. (Jetty-9.4) #6540
Issue #6497 - Replace the Alias checkers with new implementation. (Jetty-9.4) #6540
Conversation
…meFileAliasChecker Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
…lowedResourceAliasChecker
Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
…eAliasChecker Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
jetty-server/src/main/java/org/eclipse/jetty/server/SymlinkAllowedResourceAliasChecker.java
Outdated
Show resolved
Hide resolved
jetty-server/src/main/java/org/eclipse/jetty/server/SymlinkAllowedResourceAliasChecker.java
Outdated
Show resolved
Hide resolved
jetty-server/src/main/java/org/eclipse/jetty/server/SymlinkAllowedResourceAliasChecker.java
Outdated
Show resolved
Hide resolved
jetty-server/src/main/java/org/eclipse/jetty/server/SymlinkAllowedResourceAliasChecker.java
Outdated
Show resolved
Hide resolved
jetty-server/src/main/java/org/eclipse/jetty/server/handler/AllowSymLinkAliasChecker.java
Show resolved
Hide resolved
jetty-server/src/main/java/org/eclipse/jetty/server/AllowedResourceAliasChecker.java
Outdated
Show resolved
Hide resolved
jetty-server/src/main/java/org/eclipse/jetty/server/AllowedResourceAliasChecker.java
Show resolved
Hide resolved
jetty-server/src/main/java/org/eclipse/jetty/server/AllowedResourceAliasChecker.java
Outdated
Show resolved
Hide resolved
jetty-server/src/main/java/org/eclipse/jetty/server/AllowedResourceAliasChecker.java
Outdated
Show resolved
Hide resolved
Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
… not exist Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
{ | ||
for (String s : protectedTargets) | ||
{ | ||
_protectedPaths.add(new File(_basePath.toFile(), s).toPath()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a weird way to add the paths together, but doing path.resolve(...)
does not work because the protectedTarget can start with "/".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not use URIUtil.addPaths
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It has the signature (String, String)
instead of (Path, String)
, and it returns a String
instead of a Path
.
And we need the Path so we can use the linkOptions to canonicalize it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's really strange that we have a Path
, we call toFile()
, create a File
and then back to toPath()
.
I'm surprised there is not a way to just use Path
without using the File
APIs.
If the File
APIs are necessary, needs a comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The FileSystem.getPath(String first, String ... more)
API supports this call structure.
jetty-server/src/main/java/org/eclipse/jetty/server/AllowedResourceAliasChecker.java
Show resolved
Hide resolved
else if (linkOptions == NO_FOLLOW_LINKS) | ||
protect = protectedPath.normalize().toAbsolutePath().toString(); | ||
else | ||
protect = protectedPath.toFile().getCanonicalPath(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't like this code but I cant think of a better alternative. The call to toRealPath
requires the file to exist otherwise it throws, but we must check this anyway, because /web-inf
might not exist but /WEB-INF
could exist and so the Files.exists
call would return false.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we have made a rod for our backs by specifying the protected targets as lowercase /web-inf
.
However, I very much doubt that anybody other than us adds any protected targets, so why don't we just fix this!
We can change our protected targets to use the correct cases and throw an ISE if anybody calls it with any other protected targets (or if the set a protected target that doesn't exist).
Then in doStart we can find the actual Path
s of all the protected targets (that exist) and replace all this with the logic I prefer... walking up the parent links looking for any path that is sameFile to any of the protected targets
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think its a good idea, we might not think anyone is calling that method, but that's just a guess.
If someone is using it and we change it, then it will expose directories that were previously protected.
Doesn't seem like something we want going into a minor release, especially for 9.4
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually maybe we still wouldn't expose it because its still protected by ContextHander.isProtectedTarget
which is case insensitive.
Still doesn't feel like a good idea. Maybe open another issue to consider changing protectedTargets to be case sensitive. We could still change the logic of this later on, but for now it seems like its doing the job of not allowing access to protected targets.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can check if somebody sets a new protected target that does not exist, so I'm not that worried about breaking any usage of that.
The check that we want to make is that the resource is in the baseResource directory, but is not in any of the protected resource directories. We are trying to do that in a FS independent ways with case insensitive string-starts checks, but they are ultimately just hacks.
If we can precisely identify the Path
of the base resource and the Path
s of the protected resources. We can then walk the parent linkage of the resource and check that none of it's parents are sameFile as the protected resource and that ultimately one of the parents is sameFile as base resource - we then have an absolutely precise check that does exactly what we want.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we do this only in 10? ... but if we are adding new recommended checkers in 9 and deprecating the existing ones, we may as well add ones that we have 100% confidence in.
@gregw can you re-review, I had to make some additional changes since last hangout, I have explained why in the comments. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm torn!
See my comments about why we shouldn't just fix the protected targets?
{ | ||
for (String s : protectedTargets) | ||
{ | ||
_protectedPaths.add(new File(_basePath.toFile(), s).toPath()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not use URIUtil.addPaths
?
else if (linkOptions == NO_FOLLOW_LINKS) | ||
protect = protectedPath.normalize().toAbsolutePath().toString(); | ||
else | ||
protect = protectedPath.toFile().getCanonicalPath(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we have made a rod for our backs by specifying the protected targets as lowercase /web-inf
.
However, I very much doubt that anybody other than us adds any protected targets, so why don't we just fix this!
We can change our protected targets to use the correct cases and throw an ISE if anybody calls it with any other protected targets (or if the set a protected target that doesn't exist).
Then in doStart we can find the actual Path
s of all the protected targets (that exist) and replace all this with the logic I prefer... walking up the parent links looking for any path that is sameFile to any of the protected targets
…aliases Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
@lachlan-roberts So I'm thinking that perhaps we do go forward with the string version for jetty-9, but for jetty-10/11 we use #6567 as a basis and then have a version that is based entirely on |
Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
@lachlan-roberts still failing tests, can you make this PR pass the tests? |
…d at start. Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
{ | ||
for (String s : protectedTargets) | ||
{ | ||
_protectedPaths.add(new File(_basePath.toFile(), s).toPath()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's really strange that we have a Path
, we call toFile()
, create a File
and then back to toPath()
.
I'm surprised there is not a way to just use Path
without using the File
APIs.
If the File
APIs are necessary, needs a comment.
{ | ||
_basePath = getPath(_contextHandler.getBaseResource()); | ||
if (_basePath == null) | ||
_basePath = Paths.get("/").toAbsolutePath(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why calling toAbsolutePath()
? Isn't just a single /
?
Is it because of Windows? If so, needs a comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This won't do what you expect on Windows.
If you are running from a Windows Share for example, take \\machine\sharename\jetty
your code will result in a Path that is no longer in the share location.
Is that what you want?
The Path has a FileSystem object associated with it, you can ask for its root, or even for all roots that the FileSystem layer supports.
jetty-server/src/main/java/org/eclipse/jetty/server/AllowedResourceAliasChecker.java
Show resolved
Hide resolved
jetty-server/src/main/java/org/eclipse/jetty/server/AllowedResourceAliasChecker.java
Show resolved
Hide resolved
if (isProtectedPath(resourcePath, NO_FOLLOW_LINKS)) | ||
return false; | ||
|
||
if (_checkSymlinkTargets && hasSymbolicLink(resourcePath)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this check here? Should it not be in the SymlinkAllowedResourceAliasChecker
subclass?
I mean that seems weird that this base class does a bit of symlink but not all... Can't all symlink handling and configuration fields be pushed to the subclass?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a different check to what is done in the subclass, the subclass does not even use this logic, it sets _checkSymlinkTargets
to false.
This is an optional check to ensure the target of the symlink is also an allowed resource.
if (File.separatorChar == '/') | ||
addAliasCheck(new AllowSymLinkAliasChecker()); | ||
addAliasCheck(new SymlinkAllowedResourceAliasChecker(this)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why only add this for /
?
Also, the class javadoc seems to be in contrast with what is configured here in code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is tricky.
Not all FileSystems on Linux or OSX support symlinks.
And some FileSystems on Windows support symlinks.
Basing this default off of File.separatorChar is not wholly accurate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is pre-existing behaviour which we are maintaining to not break existing deployments.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be fine, because if a FS does not support symlinks, then this checker will never see a symlink and thus will be a noop
|
||
protected boolean hasSymbolicLink(Path base, Path path) | ||
{ | ||
for (Path p = path; (p != null) && !p.equals(base); p = p.getParent()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, I'm not sure the for condition !p.equals(base)
works, unless it is guaranteed that base is a normalized parent path of path
: Path.of("/home/simon/..").equals("/home") => false
.
@lachlan-roberts @sbordet @joakime I think the work being done in this PR is important, but I'm concerned that we are not converging on a solution that is easily understandable. So I think it is worthwhile to take a step back a re-describe what is being attempted here, then discuss the possible algorithms to achieve that. There are two semantics we are trying to achieve here:
I think backporting #6567 to 9 is a bit big for a dot release, but it is probably better than continuing with the mostly broken My preferred algorithm for an
I think this is a portable (somewhat expensive) check that will precisely determine if resource references a file within the real base resource that is not protected. My preferred algorithm for To achieve this, we will need to stop defining protected paths in a case insensitive way. We currently define "web-inf" and "meta-inf", which will not find the real protected targets on linux (hence this PR tries to get around it with case insensitive string checks). We can change this to "WEB-INF" and "META-INF", but if any users are use the API to add their own protected targets, then we will have a hard time telling they don't exist or are just case insensitive. I think we can work around this by: deprecating the public API and making it internal; failing to deploy if there are any user defined protected targets that do not exist in the base resource when we start. |
Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
Issue #6497
Deprecate and replace the
SameFileAliasChecker
with theAllowedResourceAliasChecker
which does some additional safety checks.This new AliasChecker is extended to also replace the
AllowSymLinkAliasChecker
withSymlinkAllowedResourceAliasChecker
.