Prevent loader classes from being served by executable war
Closes gh-5550
This commit is contained in:
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…
Reference in New Issue