Skip to content

Commit

Permalink
Issue #6277 - add testing for AliasCheckers with symlinks
Browse files Browse the repository at this point in the history
Signed-off-by: Lachlan Roberts <[email protected]>
  • Loading branch information
lachlan-roberts committed Jul 9, 2021
1 parent a1bb8f3 commit fe4ca7d
Show file tree
Hide file tree
Showing 8 changed files with 304 additions and 65 deletions.
57 changes: 57 additions & 0 deletions jetty-io/src/test/java/org/eclipse/jetty/io/IOTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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));
}
}
Original file line number Diff line number Diff line change
@@ -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
{
Expand Down Expand Up @@ -52,6 +71,7 @@ public boolean check(String uri, Resource resource)
return false;
}

// TODO: Check symlink targets if flag is set.
return true;
}
}
Original file line number Diff line number Diff line change
@@ -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."));
}
}

This file was deleted.

12 changes: 12 additions & 0 deletions tests/test-integration/src/test/resources/sibling/file
Original file line number Diff line number Diff line change
@@ -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.
12 changes: 12 additions & 0 deletions tests/test-integration/src/test/resources/webroot/documents/file
Original file line number Diff line number Diff line change
@@ -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.
Loading

0 comments on commit fe4ca7d

Please sign in to comment.