Prevent loader classes from being served by executable war

Closes gh-5550
pull/9851/head
Andy Wilkinson 7 years ago
parent 5516e8626c
commit 0ab81e4f8f

@ -86,4 +86,15 @@ public class EmbeddedServletContainerWarPackagingIntegrationTests
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
}
@Test
public void loaderClassesAreNotAvailableViaHttp() throws Exception {
ResponseEntity<String> entity = this.rest.getForEntity(
"/org/springframework/boot/loader/Launcher.class", String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
entity = this.rest.getForEntity(
"/org/springframework/../springframework/boot/loader/Launcher.class",
String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
}
}

@ -18,8 +18,11 @@ package org.springframework.boot.web.embedded.jetty;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.channels.ReadableByteChannel;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
@ -389,12 +392,14 @@ public class JettyServletWebServerFactory extends AbstractServletWebServerFactor
private void configureDocumentRoot(WebAppContext handler) {
File root = getValidDocumentRoot();
root = (root != null ? root : createTempDir("jetty-docbase"));
File docBase = (root != null ? root : createTempDir("jetty-docbase"));
try {
List<Resource> resources = new ArrayList<>();
Resource rootResource = docBase.isDirectory()
? Resource.newResource(docBase.getCanonicalFile())
: JarResource.newJarResource(Resource.newResource(docBase));
resources.add(
root.isDirectory() ? Resource.newResource(root.getCanonicalFile())
: JarResource.newJarResource(Resource.newResource(root)));
root == null ? rootResource : new LoaderHidingResource(rootResource));
for (URL resourceJarUrl : this.getUrlsOfJarsWithMetaInfResources()) {
Resource resource = createResource(resourceJarUrl);
// Jetty 9.2 and earlier do not support nested jars. See
@ -719,4 +724,93 @@ public class JettyServletWebServerFactory extends AbstractServletWebServerFactor
}
private static final class LoaderHidingResource extends Resource {
private final Resource delegate;
private LoaderHidingResource(Resource delegate) {
this.delegate = delegate;
}
@Override
public Resource addPath(String path) throws IOException, MalformedURLException {
if (path.startsWith("/org/springframework/boot")) {
return null;
}
return this.delegate.addPath(path);
}
@Override
public boolean isContainedIn(Resource resource) throws MalformedURLException {
return this.delegate.isContainedIn(resource);
}
@Override
public void close() {
close();
}
@Override
public boolean exists() {
return this.delegate.exists();
}
@Override
public boolean isDirectory() {
return this.delegate.isDirectory();
}
@Override
public long lastModified() {
return this.delegate.lastModified();
}
@Override
public long length() {
return this.delegate.length();
}
@Override
@Deprecated
public URL getURL() {
return this.delegate.getURL();
}
@Override
public File getFile() throws IOException {
return this.delegate.getFile();
}
@Override
public String getName() {
return this.delegate.getName();
}
@Override
public InputStream getInputStream() throws IOException {
return this.delegate.getInputStream();
}
@Override
public ReadableByteChannel getReadableByteChannel() throws IOException {
return this.delegate.getReadableByteChannel();
}
@Override
public boolean delete() throws SecurityException {
return this.delegate.delete();
}
@Override
public boolean renameTo(Resource dest) throws SecurityException {
return this.delegate.renameTo(dest);
}
@Override
public String[] list() {
return this.delegate.list();
}
}
}

@ -18,6 +18,8 @@ package org.springframework.boot.web.embedded.tomcat;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.ArrayList;
@ -39,16 +41,23 @@ import org.apache.catalina.Engine;
import org.apache.catalina.Host;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Manager;
import org.apache.catalina.Valve;
import org.apache.catalina.WebResource;
import org.apache.catalina.WebResourceRoot.ResourceSetType;
import org.apache.catalina.WebResourceSet;
import org.apache.catalina.Wrapper;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.loader.WebappLoader;
import org.apache.catalina.session.StandardManager;
import org.apache.catalina.startup.Tomcat;
import org.apache.catalina.startup.Tomcat.FixContextListener;
import org.apache.catalina.util.LifecycleBase;
import org.apache.catalina.webresources.AbstractResourceSet;
import org.apache.catalina.webresources.EmptyResource;
import org.apache.catalina.webresources.StandardRoot;
import org.apache.catalina.webresources.TomcatURLStreamHandlerFactory;
import org.apache.coyote.AbstractProtocol;
import org.apache.coyote.ProtocolHandler;
@ -71,6 +80,7 @@ import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils;
@ -183,12 +193,16 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto
}
protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
File docBase = getValidDocumentRoot();
docBase = (docBase != null ? docBase : createTempDir("tomcat-docbase"));
File documentRoot = getValidDocumentRoot();
final TomcatEmbeddedContext context = new TomcatEmbeddedContext();
if (documentRoot != null) {
context.setResources(new LoaderHidingResourceRoot(context));
}
context.setName(getContextPath());
context.setDisplayName(getDisplayName());
context.setPath(getContextPath());
File docBase = (documentRoot != null ? documentRoot
: createTempDir("tomcat-docbase"));
context.setDocBase(docBase.getAbsolutePath());
context.addLifecycleListener(new FixContextListener());
context.setParentClassLoader(
@ -839,4 +853,96 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto
}
private static final class LoaderHidingResourceRoot extends StandardRoot {
private LoaderHidingResourceRoot(TomcatEmbeddedContext context) {
super(context);
}
@Override
protected WebResourceSet createMainResourceSet() {
return new LoaderHidingWebResourceSet(super.createMainResourceSet());
}
}
private static final class LoaderHidingWebResourceSet extends AbstractResourceSet {
private final WebResourceSet delegate;
private final Method initInternal;
private LoaderHidingWebResourceSet(WebResourceSet delegate) {
this.delegate = delegate;
try {
this.initInternal = LifecycleBase.class.getDeclaredMethod("initInternal");
this.initInternal.setAccessible(true);
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
@Override
public WebResource getResource(String path) {
if (path.startsWith("/org/springframework/boot")) {
return new EmptyResource(getRoot(), path);
}
return this.delegate.getResource(path);
}
@Override
public String[] list(String path) {
return this.delegate.list(path);
}
@Override
public Set<String> listWebAppPaths(String path) {
return this.delegate.listWebAppPaths(path);
}
@Override
public boolean mkdir(String path) {
return this.delegate.mkdir(path);
}
@Override
public boolean write(String path, InputStream is, boolean overwrite) {
return this.delegate.write(path, is, overwrite);
}
@Override
public URL getBaseUrl() {
return this.delegate.getBaseUrl();
}
@Override
public void setReadOnly(boolean readOnly) {
this.delegate.setReadOnly(readOnly);
}
@Override
public boolean isReadOnly() {
return this.delegate.isReadOnly();
}
@Override
public void gc() {
this.delegate.gc();
}
@Override
protected void initInternal() throws LifecycleException {
if (this.delegate instanceof LifecycleBase) {
try {
ReflectionUtils.invokeMethod(this.initInternal, this.delegate);
}
catch (Exception ex) {
throw new LifecycleException(ex);
}
}
}
}
}

@ -486,13 +486,15 @@ public class UndertowServletWebServerFactory extends AbstractServletWebServerFac
}
private ResourceManager getDocumentRootResourceManager() {
File root = getCanonicalDocumentRoot();
File root = getValidDocumentRoot();
File docBase = getCanonicalDocumentRoot(root);
List<URL> metaInfResourceUrls = getUrlsOfJarsWithMetaInfResources();
List<URL> resourceJarUrls = new ArrayList<URL>();
List<ResourceManager> resourceManagers = new ArrayList<ResourceManager>();
ResourceManager rootResourceManager = root.isDirectory()
? new FileResourceManager(root, 0) : new JarResourceManager(root);
resourceManagers.add(rootResourceManager);
ResourceManager rootResourceManager = docBase.isDirectory()
? new FileResourceManager(docBase, 0) : new JarResourceManager(docBase);
resourceManagers.add(root == null ? rootResourceManager
: new LoaderHidingResourceManager(rootResourceManager));
for (URL url : metaInfResourceUrls) {
if ("file".equals(url.getProtocol())) {
File file = new File(url.getFile());
@ -518,16 +520,9 @@ public class UndertowServletWebServerFactory extends AbstractServletWebServerFac
resourceManagers.toArray(new ResourceManager[resourceManagers.size()]));
}
/**
* Return the document root in canonical form. Undertow uses File#getCanonicalFile()
* to determine whether a resource has been requested using the proper case but on
* Windows {@code java.io.tmpdir} may be set as a tilde-compressed pathname.
* @return the canonical document root
*/
private File getCanonicalDocumentRoot() {
private File getCanonicalDocumentRoot(File docBase) {
try {
File root = getValidDocumentRoot();
root = (root != null ? root : createTempDir("undertow-docbase"));
File root = docBase != null ? docBase : createTempDir("undertow-docbase");
return root.getCanonicalFile();
}
catch (IOException e) {
@ -803,4 +798,42 @@ public class UndertowServletWebServerFactory extends AbstractServletWebServerFac
}
private static final class LoaderHidingResourceManager implements ResourceManager {
private final ResourceManager delegate;
private LoaderHidingResourceManager(ResourceManager delegate) {
this.delegate = delegate;
}
@Override
public Resource getResource(String path) throws IOException {
if (path.startsWith("/org/springframework/boot")) {
return null;
}
return this.delegate.getResource(path);
}
@Override
public boolean isResourceChangeListenerSupported() {
return this.delegate.isResourceChangeListenerSupported();
}
@Override
public void registerResourceChangeListener(ResourceChangeListener listener) {
this.delegate.registerResourceChangeListener(listener);
}
@Override
public void removeResourceChangeListener(ResourceChangeListener listener) {
this.delegate.removeResourceChangeListener(listener);
}
@Override
public void close() throws IOException {
this.delegate.close();
}
}
}

Loading…
Cancel
Save