diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/IOTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/IOTest.java index 2e96a2209412..909ed0d09995 100644 --- a/jetty-io/src/test/java/org/eclipse/jetty/io/IOTest.java +++ b/jetty-io/src/test/java/org/eclipse/jetty/io/IOTest.java @@ -48,6 +48,8 @@ import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.resource.PathResource; +import org.eclipse.jetty.util.resource.Resource; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; @@ -550,4 +552,59 @@ public void testSelectorWakeup() throws Exception } } } + + @Test + public void testSymbolicLink(TestInfo testInfo) throws Exception + { + File dir = MavenTestingUtils.getTargetTestingDir(testInfo.getDisplayName()); + FS.ensureEmpty(dir); + File realFile = new File(dir, "real"); + Path realPath = realFile.toPath(); + FS.touch(realFile); + + File linkFile = new File(dir, "link"); + Path linkPath = linkFile.toPath(); + Files.createSymbolicLink(linkPath, realPath); + Path targPath = linkPath.toRealPath(); + + System.err.printf("realPath = %s%n", realPath); + System.err.printf("linkPath = %s%n", linkPath); + System.err.printf("targPath = %s%n", targPath); + + assertFalse(Files.isSymbolicLink(realPath)); + assertTrue(Files.isSymbolicLink(linkPath)); + + Resource link = new PathResource(dir).addPath("link"); + assertThat(link.isAlias(), is(true)); + } + + @Test + public void testSymbolicLinkDir(TestInfo testInfo) throws Exception + { + File dir = MavenTestingUtils.getTargetTestingDir(testInfo.getDisplayName()); + FS.ensureEmpty(dir); + + File realDirFile = new File(dir, "real"); + Path realDirPath = realDirFile.toPath(); + Files.createDirectories(realDirPath); + + File linkDirFile = new File(dir, "link"); + Path linkDirPath = linkDirFile.toPath(); + Files.createSymbolicLink(linkDirPath, realDirPath); + + File realFile = new File(realDirFile, "file"); + Path realPath = realFile.toPath(); + FS.touch(realFile); + + File linkFile = new File(linkDirFile, "file"); + Path linkPath = linkFile.toPath(); + Path targPath = linkPath.toRealPath(); + + System.err.printf("realPath = %s%n", realPath); + System.err.printf("linkPath = %s%n", linkPath); + System.err.printf("targPath = %s%n", targPath); + + assertFalse(Files.isSymbolicLink(realPath)); + assertFalse(Files.isSymbolicLink(linkPath)); + } } 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 b9a15ceda450..0315b008e4b7 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 @@ -1,15 +1,34 @@ +// +// ======================================================================== +// 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 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; - /** * This will approve an alias to any resource which is not a protected target. + * Except symlinks... */ public class AllowedResourceAliasChecker implements ContextHandler.AliasCheck { @@ -52,6 +71,7 @@ public boolean check(String uri, Resource resource) return false; } + // TODO: Check symlink targets if flag is set. return true; } } \ No newline at end of file diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/AliasCheckerSymlinkTest.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/AliasCheckerSymlinkTest.java new file mode 100644 index 000000000000..aa08c95d84f0 --- /dev/null +++ b/tests/test-integration/src/test/java/org/eclipse/jetty/test/AliasCheckerSymlinkTest.java @@ -0,0 +1,189 @@ +// +// ======================================================================== +// 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.test; + +import java.io.File; +import java.io.FileInputStream; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.AllowedResourceAliasChecker; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.servlet.DefaultServlet; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.resource.PathResource; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +public class AliasCheckerSymlinkTest +{ + private static String _fileContents; + private static Path _webRootPath; + private Server _server; + private HttpClient _client; + + private static Path getResource(String path) throws Exception + { + URL url = AliasCheckerSymlinkTest.class.getClassLoader().getResource(path); + assertNotNull(url); + return new File(url.toURI()).toPath(); + } + + private static void delete(Path path) + { + IO.delete(path.toFile()); + } + + @BeforeAll + public static void setup() throws Exception + { + _webRootPath = getResource("webroot"); + Path fileInWebroot = _webRootPath.resolve("file"); + _fileContents = IO.toString(new FileInputStream(fileInWebroot.toFile())); + + // Create symlink file that targets inside the webroot directory. + Path symlinkFile = _webRootPath.resolve("symlinkFile"); + delete(symlinkFile); + Files.createSymbolicLink(symlinkFile, fileInWebroot).toFile().deleteOnExit(); + + // Create symlink file that targets outside the webroot directory. + Path symlinkExternalFile = _webRootPath.resolve("symlinkExternalFile"); + delete(symlinkExternalFile); + Files.createSymbolicLink(symlinkExternalFile, getResource("message.txt")).toFile().deleteOnExit(); + + // Symlink to a directory inside of the webroot. + Path simlinkDir = _webRootPath.resolve("simlinkDir"); + delete(simlinkDir); + Files.createSymbolicLink(simlinkDir, _webRootPath.resolve("documents")).toFile().deleteOnExit(); + + // Symlink to a directory parent of the webroot. + Path symlinkParentDir = _webRootPath.resolve("symlinkParentDir"); + delete(symlinkParentDir); + Files.createSymbolicLink(symlinkParentDir, _webRootPath.resolve("..")).toFile().deleteOnExit(); + + // Symlink to a directory outside of the webroot. + Path symlinkSiblingDir = _webRootPath.resolve("symlinkSiblingDir"); + delete(symlinkSiblingDir); + Files.createSymbolicLink(symlinkSiblingDir, _webRootPath.resolve("../sibling")).toFile().deleteOnExit(); + + // Symlink to the WEB-INF directory. + Path webInfSymlink = _webRootPath.resolve("webInfSymlink"); + delete(webInfSymlink); + Files.createSymbolicLink(webInfSymlink, _webRootPath.resolve("WEB-INF")).toFile().deleteOnExit(); + } + + @BeforeEach + public void before() throws Exception + { + // TODO: don't use 8080 explicitly + _server = new Server(8080); + + ServletContextHandler context = new ServletContextHandler(); + context.setContextPath("/"); + context.setBaseResource(new PathResource(_webRootPath)); + context.setWelcomeFiles(new String[]{"index.html"}); + context.setProtectedTargets(new String[]{"/web-inf", "/meta-inf"}); + context.getMimeTypes().addMimeMapping("txt", "text/plain;charset=utf-8"); + + _server.setHandler(context); + context.addServlet(DefaultServlet.class, "/"); + _server.start(); + + _client = new HttpClient(); + _client.start(); + + context.addAliasCheck(new AllowedResourceAliasChecker(context)); + } + + @AfterEach + public void after() throws Exception + { + _client.stop(); + _server.stop(); + } + + // todo : no alias checker, symlink alias checker, AllowedResourceAliasChecker (not following symlinks), AllowedResourceAliasChecker (following symlinks) + @Test + public void symlinkToInsideWebroot() throws Exception + { + ContentResponse response = _client.GET("http://localhost:8080/symlinkFile"); + assertThat(response.getStatus(), is(HttpStatus.OK_200)); + assertThat(response.getContentAsString(), is(_fileContents)); + } + + @Test + public void symlinkToOutsideWebroot() throws Exception + { + ContentResponse response = _client.GET("http://localhost:8080/symlinkExternalFile"); + assertThat(response.getStatus(), is(HttpStatus.OK_200)); + assertThat(response.getContentAsString(), is(_fileContents)); + } + + @Test + public void symlinkToDirectoryInsideWebroot() throws Exception + { + ContentResponse response = _client.GET("http://localhost:8080/simlinkDir/file"); + assertThat(response.getStatus(), is(HttpStatus.OK_200)); + assertThat(response.getContentAsString(), is(_fileContents)); + } + + @Test + public void symlinkToParentDirectory() throws Exception + { + ContentResponse response = _client.GET("http://localhost:8080/symlinkParentDir/webroot/file"); + assertThat(response.getStatus(), is(HttpStatus.OK_200)); + assertThat(response.getContentAsString(), is(_fileContents)); + + response = _client.GET("http://localhost:8080/symlinkParentDir/webroot/WEB-INF/web.xml"); + assertThat(response.getStatus(), is(HttpStatus.OK_200)); + assertThat(response.getContentAsString(), is("should not be able to access this file.")); + } + + @Test + public void symlinkToSiblingDirectory() throws Exception + { + ContentResponse response = _client.GET("http://localhost:8080/symlinkSiblingDir/file"); + assertThat(response.getStatus(), is(HttpStatus.OK_200)); + assertThat(response.getContentAsString(), is(_fileContents)); + + // TODO Test .. or %2e%2e up from symlinked directory "http://localhost:8080/symlinkExternalDir/%2e%2e/webroot/file" +// ContentResponse response = _client.GET("http://localhost:8080/symlinkSiblingDir/%2e%2e/webroot/WEB-INF/web.xml"); +// assertThat(response.getStatus(), is(HttpStatus.OK_200)); +// assertThat(response.getContentAsString(), is("should not be able to access this file.")); + } + + @Test + public void symlinkToProtectedDirectoryInsideWebroot() throws Exception + { + ContentResponse response = _client.GET("http://localhost:8080/webInfSymlink/web.xml"); + assertThat(response.getStatus(), is(HttpStatus.OK_200)); + assertThat(response.getContentAsString(), is("should not be able to access this file.")); + } +} diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/TestClass.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/TestClass.java deleted file mode 100644 index 784a89319d29..000000000000 --- a/tests/test-integration/src/test/java/org/eclipse/jetty/test/TestClass.java +++ /dev/null @@ -1,62 +0,0 @@ -package org.eclipse.jetty.test; - -import org.eclipse.jetty.client.HttpClient; -import org.eclipse.jetty.server.AllowedResourceAliasChecker; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.servlet.DefaultServlet; -import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.util.resource.Resource; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.net.URI; -import java.net.URL; - -public class TestClass -{ - private Server _server; - private HttpClient _client; - - @BeforeEach - public void before() throws Exception - { - _server = new Server(8080); - - URL webRootLocation = TestClass.class.getClassLoader().getResource("webroot/index.html"); - URI webRootUri = URI.create(webRootLocation.toURI().toASCIIString().replaceFirst("/index.html$","/")); - System.err.printf("Web Root URI: %s%n",webRootUri); - - ServletContextHandler context = new ServletContextHandler(); - context.setContextPath("/"); - context.setBaseResource(Resource.newResource(webRootUri)); - context.setWelcomeFiles(new String[] { "index.html" }); - context.setProtectedTargets(new String[]{"/web-inf", "/meta-inf"}); - context.getMimeTypes().addMimeMapping("txt","text/plain;charset=utf-8"); - - context.addAliasCheck(new AllowedResourceAliasChecker(context)); - _server.setHandler(context); - context.addServlet(DefaultServlet.class,"/"); - _server.start(); - - _client = new HttpClient(); - _client.start(); - } - - - @AfterEach - public void after() throws Exception - { - _client.stop(); - _server.stop(); - } - - @Test - public void test() throws Exception - { -// ContentResponse response = _client.GET("http://localhost:8080/\\WEB-INF\\web.xml"); -// System.err.println(response); - - _server.join(); - } -} diff --git a/tests/test-integration/src/test/resources/sibling/file b/tests/test-integration/src/test/resources/sibling/file new file mode 100644 index 000000000000..516ae7511543 --- /dev/null +++ b/tests/test-integration/src/test/resources/sibling/file @@ -0,0 +1,12 @@ +Lorem ipsum dolor sit amet, consectetur adipiscing elit. In quis felis nunc. +Quisque suscipit mauris et ante auctor ornare rhoncus lacus aliquet. Pellentesque +habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. +Vestibulum sit amet felis augue, vel convallis dolor. Cras accumsan vehicula diam +at faucibus. Etiam in urna turpis, sed congue mi. Morbi et lorem eros. Donec vulputate +velit in risus suscipit lobortis. Aliquam id urna orci, nec sollicitudin ipsum. +Cras a orci turpis. Donec suscipit vulputate cursus. Mauris nunc tellus, fermentum +eu auctor ut, mollis at diam. Quisque porttitor ultrices metus, vitae tincidunt massa +sollicitudin a. Vivamus porttitor libero eget purus hendrerit cursus. Integer aliquam +consequat mauris quis luctus. Cras enim nibh, dignissim eu faucibus ac, mollis nec neque. +Aliquam purus mauris, consectetur nec convallis lacinia, porta sed ante. Suspendisse +et cursus magna. Donec orci enim, molestie a lobortis eu, imperdiet vitae neque. \ No newline at end of file diff --git a/tests/test-integration/src/test/resources/webroot/documents/file b/tests/test-integration/src/test/resources/webroot/documents/file new file mode 100644 index 000000000000..516ae7511543 --- /dev/null +++ b/tests/test-integration/src/test/resources/webroot/documents/file @@ -0,0 +1,12 @@ +Lorem ipsum dolor sit amet, consectetur adipiscing elit. In quis felis nunc. +Quisque suscipit mauris et ante auctor ornare rhoncus lacus aliquet. Pellentesque +habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. +Vestibulum sit amet felis augue, vel convallis dolor. Cras accumsan vehicula diam +at faucibus. Etiam in urna turpis, sed congue mi. Morbi et lorem eros. Donec vulputate +velit in risus suscipit lobortis. Aliquam id urna orci, nec sollicitudin ipsum. +Cras a orci turpis. Donec suscipit vulputate cursus. Mauris nunc tellus, fermentum +eu auctor ut, mollis at diam. Quisque porttitor ultrices metus, vitae tincidunt massa +sollicitudin a. Vivamus porttitor libero eget purus hendrerit cursus. Integer aliquam +consequat mauris quis luctus. Cras enim nibh, dignissim eu faucibus ac, mollis nec neque. +Aliquam purus mauris, consectetur nec convallis lacinia, porta sed ante. Suspendisse +et cursus magna. Donec orci enim, molestie a lobortis eu, imperdiet vitae neque. \ No newline at end of file diff --git a/tests/test-integration/src/test/resources/webroot/file b/tests/test-integration/src/test/resources/webroot/file new file mode 100644 index 000000000000..516ae7511543 --- /dev/null +++ b/tests/test-integration/src/test/resources/webroot/file @@ -0,0 +1,12 @@ +Lorem ipsum dolor sit amet, consectetur adipiscing elit. In quis felis nunc. +Quisque suscipit mauris et ante auctor ornare rhoncus lacus aliquet. Pellentesque +habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. +Vestibulum sit amet felis augue, vel convallis dolor. Cras accumsan vehicula diam +at faucibus. Etiam in urna turpis, sed congue mi. Morbi et lorem eros. Donec vulputate +velit in risus suscipit lobortis. Aliquam id urna orci, nec sollicitudin ipsum. +Cras a orci turpis. Donec suscipit vulputate cursus. Mauris nunc tellus, fermentum +eu auctor ut, mollis at diam. Quisque porttitor ultrices metus, vitae tincidunt massa +sollicitudin a. Vivamus porttitor libero eget purus hendrerit cursus. Integer aliquam +consequat mauris quis luctus. Cras enim nibh, dignissim eu faucibus ac, mollis nec neque. +Aliquam purus mauris, consectetur nec convallis lacinia, porta sed ante. Suspendisse +et cursus magna. Donec orci enim, molestie a lobortis eu, imperdiet vitae neque. \ No newline at end of file diff --git a/tests/test-integration/src/test/resources/webroot/org/eclipse/jetty/test.txt b/tests/test-integration/src/test/resources/webroot/org/eclipse/jetty/test.txt deleted file mode 100644 index 472c7bb03594..000000000000 --- a/tests/test-integration/src/test/resources/webroot/org/eclipse/jetty/test.txt +++ /dev/null @@ -1 +0,0 @@ -this is test .txt file and safe to access \ No newline at end of file