All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.jetty.server.AllowedResourceAliasChecker Maven / Gradle / Ivy

There is a newer version: 12.0.13
Show newest version
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

package org.eclipse.jetty.server;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.function.Supplier;

import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.resource.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 

This will approve any alias to anything inside of the {@link ContextHandler}s resource base which * is not protected by a protected target as defined by the {@link ContextHandler} protected targets at start.

*

Aliases approved by this may still be able to bypass SecurityConstraints, so this class would need to be extended * to enforce any additional security constraints that are required.

*/ public class AllowedResourceAliasChecker extends AbstractLifeCycle implements AliasCheck { private static final Logger LOG = LoggerFactory.getLogger(AllowedResourceAliasChecker.class); protected static final LinkOption[] FOLLOW_LINKS = new LinkOption[0]; protected static final LinkOption[] NO_FOLLOW_LINKS = new LinkOption[]{LinkOption.NOFOLLOW_LINKS}; private final ContextHandler _contextHandler; private final Supplier _resourceBaseSupplier; private final List _protected = new ArrayList<>(); private final AllowedResourceAliasCheckListener _listener = new AllowedResourceAliasCheckListener(); private boolean _initialized; protected Path _base; /** * @param contextHandler the context handler to use. */ public AllowedResourceAliasChecker(ContextHandler contextHandler) { this(contextHandler, contextHandler::getBaseResource); } public AllowedResourceAliasChecker(ContextHandler contextHandler, Resource baseResource) { this(contextHandler, () -> baseResource); } public AllowedResourceAliasChecker(ContextHandler contextHandler, Supplier resourceBaseSupplier) { _contextHandler = Objects.requireNonNull(contextHandler); _resourceBaseSupplier = Objects.requireNonNull(resourceBaseSupplier); } protected ContextHandler getContextHandler() { return _contextHandler; } private String[] getProtectedTargets() { return _contextHandler.getProtectedTargets(); } private void extractBaseResourceFromContext() { _base = getPath(_resourceBaseSupplier.get()); if (_base == null) return; try { if (Files.exists(_base, NO_FOLLOW_LINKS)) _base = _base.toRealPath(FOLLOW_LINKS); String[] protectedTargets = getProtectedTargets(); if (protectedTargets != null) { for (String s : protectedTargets) _protected.add(_base.getFileSystem().getPath(_base.toString(), s)); } } catch (IOException e) { LOG.warn("Base resource failure ({} is disabled): {}", this.getClass().getName(), _base, e); _base = null; } } protected void initialize() { extractBaseResourceFromContext(); _initialized = true; } @Override protected void doStart() throws Exception { // We can only initialize if ContextHandler in started state, the baseResource can be changed even in starting state. // If the ContextHandler is not started add a listener to delay initialization until fully started. if (_contextHandler.isStarted()) initialize(); else _contextHandler.addEventListener(_listener); } @Override protected void doStop() throws Exception { _contextHandler.removeEventListener(_listener); _base = null; _protected.clear(); } @Override public boolean checkAlias(String pathInContext, Resource resource) { if (!_initialized) extractBaseResourceFromContext(); if (_base == null) return false; try { // The existence check resolves the symlinks. if (!resource.exists()) return false; Path path = getPath(resource); if (path == null) return false; return check(pathInContext, path); } catch (Throwable t) { if (LOG.isDebugEnabled()) LOG.debug("Failed to check alias", t); return false; } } protected boolean check(String pathInContext, Path path) { // Allow any aliases (symlinks, 8.3, casing, etc.) so long as // the resulting real file is allowed. return isAllowed(getRealPath(path)); } protected boolean isAllowed(Path path) { // If the resource doesn't exist we cannot determine whether it is protected, so we assume it is. if (path != null && Files.exists(path)) { // Walk the path parent links looking for the base resource, but failing if any steps are protected while (path != null) { // If the path is the same file as the base, then it is contained in the base and // is not protected. if (isSameFile(path, _base)) return true; // If the path is the same file as any protected resources, then it is protected. for (Path p : _protected) { if (isSameFile(path, p)) return false; } // Walks up the aliased path name, not the real path name. // If WEB-INF is a link to /var/lib/webmeta then after checking // a URI of /WEB-INF/file.xml the parent is /WEB-INF and not /var/lib/webmeta path = path.getParent(); } } return false; } protected boolean isSameFile(Path path1, Path path2) { if (Objects.equals(path1, path2)) return true; try { if (Files.isSameFile(path1, path2)) return true; } catch (Throwable t) { if (LOG.isDebugEnabled()) LOG.debug("ignored", t); } return false; } private static Path getRealPath(Path path) { if (path == null || !Files.exists(path)) return null; try { path = path.toRealPath(FOLLOW_LINKS); if (Files.exists(path)) return path; } catch (IOException e) { if (LOG.isDebugEnabled()) LOG.debug("No real path for {}", path, e); } return null; } protected Path getPath(Resource resource) { try { return (resource == null) ? null : resource.getPath(); } catch (Throwable t) { LOG.trace("getPath() failed", t); return null; } } private class AllowedResourceAliasCheckListener implements LifeCycle.Listener { @Override public void lifeCycleStarted(LifeCycle event) { initialize(); } } @Override public String toString() { String[] protectedTargets = getProtectedTargets(); return String.format("%s@%x{base=%s,protected=%s}", this.getClass().getSimpleName(), hashCode(), _base, (protectedTargets == null) ? null : Arrays.asList(protectedTargets)); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy