VYPR
High severity8.8NVD Advisory· Published Feb 25, 2016· Updated May 6, 2026

CVE-2016-0714

CVE-2016-0714

Description

The session-persistence implementation in Apache Tomcat 6.x before 6.0.45, 7.x before 7.0.68, 8.x before 8.0.31, and 9.x before 9.0.0.M2 mishandles session attributes, which allows remote authenticated users to bypass intended SecurityManager restrictions and execute arbitrary code in a privileged context via a web application that places a crafted object in a session.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
org.apache.tomcat:tomcatMaven
>= 9.0.0.M1, < 9.0.0.M29.0.0.M2
org.apache.tomcat:tomcatMaven
>= 8.0.0.RC1, < 8.0.328.0.32
org.apache.tomcat:tomcatMaven
>= 7.0.0, < 7.0.707.0.70
org.apache.tomcat:tomcatMaven
>= 6.0.0, < 6.0.466.0.46

Affected products

104
  • Apache/Tomcat98 versions
    cpe:2.3:a:apache:tomcat:6.0.0:*:*:*:*:*:*:*+ 97 more
    • cpe:2.3:a:apache:tomcat:6.0.0:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.0:alpha:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.1:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.10:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.11:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.13:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.14:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.16:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.18:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.1:alpha:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.2:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.20:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.24:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.26:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.28:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.29:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.2:alpha:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.2:beta:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.30:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.32:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.33:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.35:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.36:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.37:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.39:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.4:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.41:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.43:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.44:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:6.0.4:alpha:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.0:beta:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.10:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.11:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.12:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.14:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.16:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.19:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.20:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.21:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.22:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.23:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.25:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.26:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.27:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.28:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.29:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.2:beta:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.30:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.32:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.33:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.34:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.35:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.37:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.39:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.40:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.41:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.42:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.47:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.4:beta:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.50:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.52:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.53:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.54:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.55:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.56:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.57:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.59:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.5:beta:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.6:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.61:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.62:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.63:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.64:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.65:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:7.0.67:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:8.0.0:rc1:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:8.0.0:rc10:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:8.0.0:rc3:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:8.0.0:rc5:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:8.0.1:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:8.0.11:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:8.0.12:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:8.0.14:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:8.0.15:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:8.0.17:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:8.0.18:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:8.0.20:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:8.0.21:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:8.0.22:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:8.0.23:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:8.0.24:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:8.0.26:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:8.0.27:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:8.0.28:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:8.0.29:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:8.0.3:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:8.0.30:*:*:*:*:*:*:*
    • cpe:2.3:a:apache:tomcat:9.0.0:milestone1:*:*:*:*:*:*
  • cpe:2.3:o:canonical:ubuntu_linux:12.04:*:*:*:lts:*:*:*+ 3 more
    • cpe:2.3:o:canonical:ubuntu_linux:12.04:*:*:*:lts:*:*:*
    • cpe:2.3:o:canonical:ubuntu_linux:14.04:*:*:*:lts:*:*:*
    • cpe:2.3:o:canonical:ubuntu_linux:15.10:*:*:*:*:*:*:*
    • cpe:2.3:o:canonical:ubuntu_linux:16.04:*:*:*:lts:*:*:*
  • cpe:2.3:o:debian:debian_linux:7.0:*:*:*:*:*:*:*+ 1 more
    • cpe:2.3:o:debian:debian_linux:7.0:*:*:*:*:*:*:*
    • cpe:2.3:o:debian:debian_linux:8.0:*:*:*:*:*:*:*

Patches

8
ff1b659dc366

When using the new sessionAttributeValueClassNameFilter, apply the filter earlier rather than loading the class and then deciding to filter it out.

https://github.com/apache/tomcatMark ThomasJan 27, 2016via ghsa
8 files changed · +153 14
  • java/org/apache/catalina/session/ManagerBase.java+25 1 modified
    @@ -39,6 +39,7 @@
     import org.apache.catalina.Container;
     import org.apache.catalina.Context;
     import org.apache.catalina.Engine;
    +import org.apache.catalina.Globals;
     import org.apache.catalina.Lifecycle;
     import org.apache.catalina.LifecycleException;
     import org.apache.catalina.Manager;
    @@ -234,8 +235,31 @@ public abstract class ManagerBase extends LifecycleMBeanBase implements Manager
         private boolean warnOnSessionAttributeFilterFailure;
     
     
    -    // ------------------------------------------------------------- Properties
    +    // ------------------------------------------------------------ Constructors
     
    +    public ManagerBase() {
    +        if (Globals.IS_SECURITY_ENABLED) {
    +            // Minimum set required for default distribution/persistence to work
    +            // plus String
    +            setSessionAttributeValueClassNameFilter(
    +                    "java\\.lang\\.(?:Boolean|Integer|Long|Number|String)");
    +            setWarnOnSessionAttributeFilterFailure(true);
    +        }
    +    }
    +
    +
    +    // -------------------------------------------------------------- Properties
    +
    +    /**
    +     * Obtain the regular expression used to filter session attribute based on
    +     * attribute name. The regular expression is anchored so it must match the
    +     * entire name
    +     *
    +     * @return The regular expression currently used to filter attribute names.
    +     *         {@code null} means no filter is applied. If an empty string is
    +     *         specified then no names will match the filter and all attributes
    +     *         will be blocked.
    +     */
         public String getSessionAttributeNameFilter() {
             if (sessionAttributeNamePattern == null) {
                 return null;
    
  • java/org/apache/catalina/session/StandardManager.java+5 1 modified
    @@ -208,17 +208,21 @@ protected void doLoad() throws ClassNotFoundException, IOException {
             ObjectInputStream ois = null;
             Loader loader = null;
             ClassLoader classLoader = null;
    +        Log logger = null;
             try {
                 fis = new FileInputStream(file.getAbsolutePath());
                 bis = new BufferedInputStream(fis);
                 loader = container.getLoader();
    +            logger = container.getLogger();
                 if (loader != null) {
                     classLoader = loader.getClassLoader();
                 }
                 if (classLoader == null) {
                     classLoader = getClass().getClassLoader();
                 }
    -            ois = new CustomObjectInputStream(bis, classLoader);
    +            ois = new CustomObjectInputStream(bis, classLoader, logger,
    +                    getSessionAttributeValueClassNamePattern(),
    +                    getWarnOnSessionAttributeFilterFailure());
             } catch (FileNotFoundException e) {
                 if (log.isDebugEnabled()) {
                     log.debug("No persisted data file found");
    
  • java/org/apache/catalina/session/StoreBase.java+14 1 modified
    @@ -228,7 +228,20 @@ public void processExpires() {
          */
         protected ObjectInputStream getObjectInputStream(InputStream is) throws IOException {
             BufferedInputStream bis = new BufferedInputStream(is);
    -        return new CustomObjectInputStream(bis, Thread.currentThread().getContextClassLoader());
    +
    +        CustomObjectInputStream ois;
    +        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    +
    +        if (manager instanceof ManagerBase) {
    +            ManagerBase managerBase = (ManagerBase) manager;
    +            ois = new CustomObjectInputStream(bis, classLoader, manager.getContainer().getLogger(),
    +                    managerBase.getSessionAttributeValueClassNamePattern(),
    +                    managerBase.getWarnOnSessionAttributeFilterFailure());
    +        } else {
    +            ois = new CustomObjectInputStream(bis, classLoader);
    +        }
    +
    +        return ois;
         }
     
     
    
  • java/org/apache/catalina/util/CustomObjectInputStream.java+85 2 modified
    @@ -18,9 +18,18 @@
     
     import java.io.IOException;
     import java.io.InputStream;
    +import java.io.InvalidClassException;
     import java.io.ObjectInputStream;
     import java.io.ObjectStreamClass;
     import java.lang.reflect.Proxy;
    +import java.util.Collections;
    +import java.util.Set;
    +import java.util.WeakHashMap;
    +import java.util.concurrent.ConcurrentHashMap;
    +import java.util.regex.Pattern;
    +
    +import org.apache.juli.logging.Log;
    +import org.apache.tomcat.util.res.StringManager;
     
     /**
      * Custom subclass of <code>ObjectInputStream</code> that loads from the
    @@ -32,23 +41,81 @@
      */
     public final class CustomObjectInputStream extends ObjectInputStream {
     
    +    private static final StringManager sm = StringManager.getManager(CustomObjectInputStream.class);
    +
    +    private static final WeakHashMap<ClassLoader, Set<String>> reportedClassCache =
    +            new WeakHashMap<ClassLoader, Set<String>>();
    +
         /**
          * The class loader we will use to resolve classes.
          */
         private ClassLoader classLoader = null;
    +    private final Set<String> reportedClasses;
    +    private final Log log;
    +
    +    private final Pattern allowedClassNamePattern;
    +    private final String allowedClassNameFilter;
    +    private final boolean warnOnFailure;
     
     
         /**
    -     * Construct a new instance of CustomObjectInputStream
    +     * Construct a new instance of CustomObjectInputStream without any filtering
    +     * of deserialized classes.
          *
          * @param stream The input stream we will read from
          * @param classLoader The class loader used to instantiate objects
          *
          * @exception IOException if an input/output error occurs
          */
         public CustomObjectInputStream(InputStream stream, ClassLoader classLoader) throws IOException {
    +        this(stream, classLoader, null, null, false);
    +    }
    +
    +
    +    /**
    +     * Construct a new instance of CustomObjectInputStream with filtering of
    +     * deserialized classes.
    +     *
    +     * @param stream The input stream we will read from
    +     * @param classLoader The class loader used to instantiate objects
    +     * @param log The logger to use to report any issues. It may only be null if
    +     *            the filterMode does not require logging
    +     * @param allowedClassNamePattern The regular expression to use to filter
    +     *                                deserialized classes. The fully qualified
    +     *                                class name must match this pattern for
    +     *                                deserialization to be allowed if filtering
    +     *                                is enabled.
    +     * @param warnOnFailure Should any failures be logged?
    +     *
    +     * @exception IOException if an input/output error occurs
    +     */
    +    public CustomObjectInputStream(InputStream stream, ClassLoader classLoader,
    +            Log log, Pattern allowedClassNamePattern, boolean warnOnFailure)
    +            throws IOException {
             super(stream);
    +        if (log == null && allowedClassNamePattern != null && warnOnFailure) {
    +            throw new IllegalArgumentException(
    +                    sm.getString("customObjectInputStream.logRequired"));
    +        }
             this.classLoader = classLoader;
    +        this.log = log;
    +        this.allowedClassNamePattern = allowedClassNamePattern;
    +        if (allowedClassNamePattern == null) {
    +            this.allowedClassNameFilter = null;
    +        } else {
    +            this.allowedClassNameFilter = allowedClassNamePattern.toString();
    +        }
    +        this.warnOnFailure = warnOnFailure;
    +
    +        Set<String> reportedClasses;
    +        synchronized (reportedClassCache) {
    +            reportedClasses = reportedClassCache.get(classLoader);
    +            if (reportedClasses == null) {
    +                reportedClasses = Collections.newSetFromMap(new ConcurrentHashMap<String,Boolean>());
    +                reportedClassCache.put(classLoader, reportedClasses);
    +            }
    +        }
    +        this.reportedClasses = reportedClasses;
         }
     
     
    @@ -64,8 +131,24 @@ public CustomObjectInputStream(InputStream stream, ClassLoader classLoader) thro
         @Override
         public Class<?> resolveClass(ObjectStreamClass classDesc)
             throws ClassNotFoundException, IOException {
    +
    +        String name = classDesc.getName();
    +        if (allowedClassNamePattern != null) {
    +            boolean allowed = allowedClassNamePattern.matcher(name).matches();
    +            if (!allowed) {
    +                boolean doLog = warnOnFailure && reportedClasses.add(name);
    +                String msg = sm.getString("customObjectInputStream.nomatch", name, allowedClassNameFilter);
    +                if (doLog) {
    +                    log.warn(msg);
    +                } else if (log.isDebugEnabled()) {
    +                    log.debug(msg);
    +                }
    +                throw new InvalidClassException(msg);
    +            }
    +        }
    +
             try {
    -            return Class.forName(classDesc.getName(), false, classLoader);
    +            return Class.forName(name, false, classLoader);
             } catch (ClassNotFoundException e) {
                 try {
                     // Try also the superclass because of primitive types
    
  • java/org/apache/catalina/util/LocalStrings.properties+2 0 modified
    @@ -17,6 +17,8 @@ parameterMap.locked=No modifications are allowed to a locked ParameterMap
     resourceSet.locked=No modifications are allowed to a locked ResourceSet
     hexUtil.bad=Bad hexadecimal digit
     hexUtil.odd=Odd number of hexadecimal digits
    +customObjectInputStream.logRequired=A valid logger is required for class name filtering with logging
    +customObjectInputStream.nomatch=The class [{0}] did not match the regular expression [{1}] for classes allowed to be deserialized
     #Default Messages Utilized by the ExtensionValidator
     extensionValidator.web-application-manifest=Web Application Manifest
     extensionValidator.extension-not-found-error=ExtensionValidator[{0}][{1}]: Required extension [{2}] not found.
    
  • webapps/docs/changelog.xml+2 1 modified
    @@ -177,7 +177,8 @@
             based on the implementation class of the value and optional
             <code>WARN</code> level logging if an attribute is filtered. These
             options are avaialble for all of the Manager implementations that ship
    -        with Tomcat. (markt)
    +        with Tomcat. When a <code>SecurityManager</code> is used filtering will
    +        be enabled by default. (markt)
           </add>
         </changelog>
       </subsection>
    
  • webapps/docs/config/cluster-manager.xml+10 4 modified
    @@ -203,7 +203,9 @@
             length or <code>null</code>, all attributes are eligible for
             replication. The pattern is anchored so the fully qualified class name
             must fully match the pattern. If not specified, the default value of
    -        <code>null</code> will be used.</p>
    +        <code>null</code> will be used unless a <code>SecurityManager</code> is
    +        enabled in which case the default will be
    +        <code>java\\.lang\\.(?:Boolean|Integer|Long|Number|String)</code>.</p>
           </attribute>
           <attribute name="stateTimestampDrop" required="false">
             When this node sends a <code>GET_ALL_SESSIONS</code> message to other
    @@ -222,7 +224,8 @@
             attribute, should this be logged at <code>WARN</code> level? If
             <code>WARN</code> level logging is disabled then it will be logged at
             <code>DEBUG</code>. The default value of this attribute is
    -        <code>false</code>.</p>
    +        <code>false</code> unless a <code>SecurityManager</code> is enabled in
    +        which case the default will be <code>true</code>.</p>
           </attribute>
         </attributes>
       </subsection>
    @@ -265,7 +268,9 @@
             length or <code>null</code>, all attributes are eligible for
             replication. The pattern is anchored so the fully qualified class name
             must fully match the pattern. If not specified, the default value of
    -        <code>null</code> will be used.</p>
    +        <code>null</code> will be used unless a <code>SecurityManager</code> is
    +        enabled in which case the default will be
    +        <code>java\\.lang\\.(?:Boolean|Integer|Long|Number|String)</code>.</p>
           </attribute>
           <attribute name="terminateOnStartFailure" required="false">
             Set to true if you wish to terminate replication map when replication
    @@ -280,7 +285,8 @@
             attribute, should this be logged at <code>WARN</code> level? If
             <code>WARN</code> level logging is disabled then it will be logged at
             <code>DEBUG</code>. The default value of this attribute is
    -        <code>false</code>.</p>
    +        <code>false</code> unless a <code>SecurityManager</code> is enabled in
    +        which case the default will be <code>true</code>.</p>
           </attribute>
         </attributes>
       </subsection>
    
  • webapps/docs/config/manager.xml+10 4 modified
    @@ -198,7 +198,9 @@
             length or <code>null</code>, all attributes are eligible for
             distribution. The pattern is anchored so the fully qualified class name
             must fully match the pattern. If not specified, the default value of
    -        <code>null</code> will be used.</p>
    +        <code>null</code> will be used unless a <code>SecurityManager</code> is
    +        enabled in which case the default will be
    +        <code>java\\.lang\\.(?:Boolean|Integer|Long|Number|String)</code>.</p>
           </attribute>
     
           <attribute name="warnOnSessionAttributeFilterFailure" required="false">
    @@ -207,7 +209,8 @@
             attribute, should this be logged at <code>WARN</code> level? If
             <code>WARN</code> level logging is disabled then it will be logged at
             <code>DEBUG</code>. The default value of this attribute is
    -        <code>false</code>.</p>
    +        <code>false</code> unless a <code>SecurityManager</code> is enabled in
    +        which case the default will be <code>true</code>.</p>
           </attribute>
         </attributes>
     
    @@ -318,7 +321,9 @@
             length or <code>null</code>, all attributes are eligible for
             distribution. The pattern is anchored so the fully qualified class name
             must fully match the pattern. If not specified, the default value of
    -        <code>null</code> will be used.</p>
    +        <code>null</code> will be used unless a <code>SecurityManager</code> is
    +        enabled in which case the default will be
    +        <code>java\\.lang\\.(?:Boolean|Integer|Long|Number|String)</code>.</p>
           </attribute>
     
           <attribute name="warnOnSessionAttributeFilterFailure" required="false">
    @@ -327,7 +332,8 @@
             attribute, should this be logged at <code>WARN</code> level? If
             <code>WARN</code> level logging is disabled then it will be logged at
             <code>DEBUG</code>. The default value of this attribute is
    -        <code>false</code>.</p>
    +        <code>false</code> unless a <code>SecurityManager</code> is enabled in
    +        which case the default will be <code>true</code>.</p>
           </attribute>
         </attributes>
     
    
79e8ad03404c

Expand the session attribute filtering options

https://github.com/apache/tomcatMark ThomasJan 26, 2016via ghsa
8 files changed · +258 20
  • java/org/apache/catalina/ha/session/ClusterManagerBase.java+3 1 modified
    @@ -193,7 +193,9 @@ protected void clone(ClusterManagerBase copy) {
             copy.setMaxInactiveInterval(getMaxInactiveInterval());
             copy.setProcessExpiresFrequency(getProcessExpiresFrequency());
             copy.setNotifyListenersOnReplication(isNotifyListenersOnReplication());
    -        copy.setSessionAttributeFilter(getSessionAttributeFilter());
    +        copy.setSessionAttributeNameFilter(getSessionAttributeNameFilter());
    +        copy.setSessionAttributeValueClassNameFilter(getSessionAttributeValueClassNameFilter());
    +        copy.setWarnOnSessionAttributeFilterFailure(getWarnOnSessionAttributeFilterFailure());
             copy.setSecureRandomClass(getSecureRandomClass());
             copy.setSecureRandomProvider(getSecureRandomProvider());
             copy.setSecureRandomAlgorithm(getSecureRandomAlgorithm());
    
  • java/org/apache/catalina/ha/session/mbeans-descriptors.xml+16 0 modified
    @@ -345,6 +345,14 @@
           name="sessionAttributeNameFilter"
           descritpion="The string pattern used for including session attributes in replication. Null means all attributes are included."
           type="java.lang.String"/>
    +    <attribute
    +      name="sessionAttributeValueClassNameFilter"
    +      description="The regular expression used to filter session attributes based on the implementation class of the value. The regular expression is anchored and must match the fully qualified class name."
    +      type="java.lang.String"/>
    +    <attribute
    +      name="warnOnSessionAttributeFilterFailure"
    +      description="Should a WARN level log message be generated if a session attribute fails to match sessionAttributeNameFilter or sessionAttributeClassNameFilter?"
    +      type="boolean"/>
         <operation
           name="expireSession"
           description="Expired the given session"
    @@ -576,6 +584,14 @@
           name="sessionAttributeNameFilter"
           descritpion="The string pattern used for including session attributes in replication. Null means all attributes are included."
           type="java.lang.String"/>
    +    <attribute
    +      name="sessionAttributeValueClassNameFilter"
    +      description="The regular expression used to filter session attributes based on the implementation class of the value. The regular expression is anchored and must match the fully qualified class name."
    +      type="java.lang.String"/>
    +    <attribute
    +      name="warnOnSessionAttributeFilterFailure"
    +      description="Should a WARN level log message be generated if a session attribute fails to match sessionAttributeNameFilter or sessionAttributeClassNameFilter?"
    +      type="boolean"/>
         <operation
           name="expireSession"
           description="Expired the given session"
    
  • java/org/apache/catalina/session/LocalStrings.properties+2 0 modified
    @@ -35,6 +35,8 @@ JDBCStore.commitSQLException=SQLException committing connection before closing
     managerBase.createRandom=Created random number generator for session ID generation in {0}ms.
     managerBase.contextNull=The Container must be set to a non-null Context instance before the Manager is used
     managerBase.createSession.ise=createSession: Too many active sessions
    +managerBase.sessionAttributeNameFilter=Skipped session attribute named [{0}] because it did not match the name filter [{1}]
    +managerBase.sessionAttributeValueClassNameFilter=Skipped session attribute named [{0}] because the value type [{1}] did not match the filter [{2}]
     managerBase.sessionTimeout=Invalid session timeout setting {0}
     managerBase.setContextNotNew=It is illegal to call setContext() to change the Context associated with a Manager if the Manager is not in the NEW state
     serverSession.value.iae=null value
    
  • java/org/apache/catalina/session/ManagerBase.java+116 4 modified
    @@ -35,6 +35,7 @@
     import java.util.concurrent.ConcurrentHashMap;
     import java.util.concurrent.atomic.AtomicLong;
     import java.util.regex.Pattern;
    +import java.util.regex.PatternSyntaxException;
     
     import org.apache.catalina.Container;
     import org.apache.catalina.Context;
    @@ -222,6 +223,10 @@ public abstract class ManagerBase extends LifecycleMBeanBase
         
         private Pattern sessionAttributeNamePattern;
     
    +    private Pattern sessionAttributeValueClassNamePattern;
    +
    +    private boolean warnOnSessionAttributeFilterFailure;
    +
     
         // ------------------------------------------------------------- Properties
     
    @@ -248,8 +253,86 @@ protected Pattern getSessionAttributeNamePattern() {
     
     
         /**
    -     * Return the Container with which this Manager is associated.
    +     * Obtain the regular expression used to filter session attribute based on
    +     * the implementation class of the value. The regular expression is anchored
    +     * and must match the fully qualified class name.
    +     *
    +     * @return The regular expression currently used to filter class names.
    +     *         {@code null} means no filter is applied. If an empty string is
    +     *         specified then no names will match the filter and all attributes
    +     *         will be blocked.
    +     */
    +    public String getSessionAttributeValueClassNameFilter() {
    +        if (sessionAttributeValueClassNamePattern == null) {
    +            return null;
    +        }
    +        return sessionAttributeValueClassNamePattern.toString();
    +    }
    +
    +
    +    /**
    +     * Provides {@link #getSessionAttributeValueClassNameFilter()} as a
    +     * pre-compiled regular expression pattern.
    +     *
    +     * @return The pre-compiled pattern used to filter session attributes based
    +     *         on the implementation class name of the value. {@code null} means
    +     *         no filter is applied.
    +     */
    +    protected Pattern getSessionAttributeValueClassNamePattern() {
    +        return sessionAttributeValueClassNamePattern;
    +    }
    +
    +
    +    /**
    +     * Set the regular expression to use to filter classes used for session
    +     * attributes. The regular expression is anchored and must match the fully
    +     * qualified class name.
    +     *
    +     * @param sessionAttributeValueClassNameFilter The regular expression to use
    +     *            to filter session attributes based on class name. Use {@code
    +     *            null} if no filtering is required. If an empty string is
    +     *           specified then no names will match the filter and all
    +     *           attributes will be blocked.
    +     *
    +     * @throws PatternSyntaxException If the expression is not valid
    +     */
    +    public void setSessionAttributeValueClassNameFilter(String sessionAttributeValueClassNameFilter)
    +            throws PatternSyntaxException {
    +        if (sessionAttributeValueClassNameFilter == null ||
    +                sessionAttributeValueClassNameFilter.length() == 0) {
    +            sessionAttributeValueClassNamePattern = null;
    +        } else {
    +            sessionAttributeValueClassNamePattern =
    +                    Pattern.compile(sessionAttributeValueClassNameFilter);
    +        }
    +    }
    +
    +
    +    /**
    +     * Should a warn level log message be generated if a session attribute is
    +     * not persisted / replicated / restored.
    +     *
    +     * @return {@code true} if a warn level log message should be generated
    +     */
    +    public boolean getWarnOnSessionAttributeFilterFailure() {
    +        return warnOnSessionAttributeFilterFailure;
    +    }
    +
    +
    +    /**
    +     * Configure whether or not a warn level log message should be generated if
    +     * a session attribute is not persisted / replicated / restored.
    +     *
    +     * @param warnOnSessionAttributeFilterFailure {@code true} if the
    +     *            warn level message should be generated
    +     *
          */
    +    public void setWarnOnSessionAttributeFilterFailure(
    +            boolean warnOnSessionAttributeFilterFailure) {
    +        this.warnOnSessionAttributeFilterFailure = warnOnSessionAttributeFilterFailure;
    +    }
    +
    +
         @Override
         public Container getContainer() {
             return this.container;
    @@ -767,10 +850,39 @@ public void changeSessionId(Session session) {
         @Override
         public boolean willAttributeDistribute(String name, Object value) {
             Pattern sessionAttributeNamePattern = getSessionAttributeNamePattern();
    -        if (sessionAttributeNamePattern == null) {
    -            return true;
    +        if (sessionAttributeNamePattern != null) {
    +            if (!sessionAttributeNamePattern.matcher(name).matches()) {
    +                if (getWarnOnSessionAttributeFilterFailure() || log.isDebugEnabled()) {
    +                    String msg = sm.getString("managerBase.sessionAttributeNameFilter",
    +                            name, sessionAttributeNamePattern);
    +                    if (getWarnOnSessionAttributeFilterFailure()) {
    +                        log.warn(msg);
    +                    } else {
    +                        log.debug(msg);
    +                    }
    +                }
    +                return false;
    +            }
             }
    -        return sessionAttributeNamePattern.matcher(name).matches();
    +
    +        Pattern sessionAttributeValueClassNamePattern = getSessionAttributeValueClassNamePattern();
    +        if (value != null && sessionAttributeValueClassNamePattern != null) {
    +            if (!sessionAttributeValueClassNamePattern.matcher(
    +                    value.getClass().getName()).matches()) {
    +                if (getWarnOnSessionAttributeFilterFailure() || log.isDebugEnabled()) {
    +                    String msg = sm.getString("managerBase.sessionAttributeValueClassNameFilter",
    +                            name, value.getClass().getName(), sessionAttributeNamePattern);
    +                    if (getWarnOnSessionAttributeFilterFailure()) {
    +                        log.warn(msg);
    +                    } else {
    +                        log.debug(msg);
    +                    }
    +                }
    +                return false;
    +            }
    +        }
    +
    +        return true;
         }
     
     
    
  • java/org/apache/catalina/session/mbeans-descriptors.xml+16 0 modified
    @@ -137,6 +137,14 @@
               descritpion="The string pattern used for including session attributes in distribution. Null means all attributes are included."
                      type="java.lang.String"/>
     
    +    <attribute   name="sessionAttributeValueClassNameFilter"
    +          description="The regular expression used to filter session attributes based on the implementation class of the value. The regular expression is anchored and must match the fully qualified class name."
    +                 type="java.lang.String"/>
    +
    +    <attribute   name="warnOnSessionAttributeFilterFailure"
    +          description="Should a WARN level log message be generated if a session attribute fails to match sessionAttributeNameFilter or sessionAttributeClassNameFilter?"
    +                 type="boolean"/>
    +
         <operation   name="backgroundProcess"
               description="Invalidate all sessions that have expired."
                    impact="ACTION"
    @@ -329,6 +337,14 @@
               descritpion="The string pattern used for including session attributes in distribution. Null means all attributes are included."
                      type="java.lang.String"/>
     
    +    <attribute   name="sessionAttributeValueClassNameFilter"
    +          description="The regular expression used to filter session attributes based on the implementation class of the value. The regular expression is anchored and must match the fully qualified class name."
    +                 type="java.lang.String"/>
    +
    +    <attribute   name="warnOnSessionAttributeFilterFailure"
    +          description="Should a WARN level log message be generated if a session attribute fails to match sessionAttributeNameFilter or sessionAttributeClassNameFilter?"
    +                 type="boolean"/>
    +
         <operation   name="backgroundProcess"
               description="Invalidate all sessions that have expired."
                    impact="ACTION"
    
  • webapps/docs/changelog.xml+7 0 modified
    @@ -172,6 +172,13 @@
             well as unload to ensure that configuration changes made while the web
             application is stopped are applied to any persisted data. (markt)
           </add>
    +      <add>
    +        Extend the session attribute filtering options to include filtering
    +        based on the implementation class of the value and optional
    +        <code>WARN</code> level logging if an attribute is filtered. These
    +        options are avaialble for all of the Manager implementations that ship
    +        with Tomcat. (markt)
    +      </add>
         </changelog>
       </subsection>
       <subsection name="Jasper">
    
  • webapps/docs/config/cluster-manager.xml+56 11 modified
    @@ -83,17 +83,6 @@
             sessions to expire on all nodes when a shutdown occurs on one node, set
             this value to <code>true</code>. Default value is <code>false</code>.
           </attribute>
    -      <attribute name="sessionAttributeNameFilter" required="false">
    -        A regular expression used to filter which session attributes will be
    -        replicated. An attribute will only be replicated if its name matches
    -        this pattern. If the pattern is zero length or <code>null</code>, all
    -        attributes are eligible for replication. The pattern is anchored so the
    -        session attribute name must fully match the pattern. As an example, the
    -        value <code>(userName|sessionHistory)</code> will only replicate the
    -        two session attributes named <code>userName</code> and
    -        <code>sessionHistory</code>. If not specified, the default value of
    -        <code>null</code> will be used.
    -      </attribute>
           <attribute name="maxInactiveInterval" required="false">
             <p>The initial maximum time interval, in seconds,
             between client requests before a session is invalidated. A negative value
    @@ -196,6 +185,26 @@
             effective only when <code>sendAllSessions</code> is <code>false</code>.
             Default is <code>2000</code> milliseconds.
           </attribute>
    +      <attribute name="sessionAttributeNameFilter" required="false">
    +        <p>A regular expression used to filter which session attributes will be
    +        replicated. An attribute will only be replicated if its name matches
    +        this pattern. If the pattern is zero length or <code>null</code>, all
    +        attributes are eligible for replication. The pattern is anchored so the
    +        session attribute name must fully match the pattern. As an example, the
    +        value <code>(userName|sessionHistory)</code> will only replicate the
    +        two session attributes named <code>userName</code> and
    +        <code>sessionHistory</code>. If not specified, the default value of
    +        <code>null</code> will be used.</p>
    +      </attribute>
    +      <attribute name="sessionAttributeValueClassNameFilter" required="false">
    +        <p>A regular expression used to filter which session attributes will be
    +        replicated. An attribute will only be replicated if the implementation
    +        class name of the value matches this pattern. If the pattern is zero
    +        length or <code>null</code>, all attributes are eligible for
    +        replication. The pattern is anchored so the fully qualified class name
    +        must fully match the pattern. If not specified, the default value of
    +        <code>null</code> will be used.</p>
    +      </attribute>
           <attribute name="stateTimestampDrop" required="false">
             When this node sends a <code>GET_ALL_SESSIONS</code> message to other
             node, all session messages that are received as a response are queued.
    @@ -207,6 +216,14 @@
             If set to <code>false</code>, all queued session messages are handled.
             Default is <code>true</code>.
           </attribute>
    +      <attribute name="warnOnSessionAttributeFilterFailure" required="false">
    +        <p>If <strong>sessionAttributeNameFilter</strong> or
    +        <strong>sessionAttributeValueClassNameFilter</strong> blocks an
    +        attribute, should this be logged at <code>WARN</code> level? If
    +        <code>WARN</code> level logging is disabled then it will be logged at
    +        <code>DEBUG</code>. The default value of this attribute is
    +        <code>false</code>.</p>
    +      </attribute>
         </attributes>
       </subsection>
       <subsection name="org.apache.catalina.ha.session.BackupManager Attributes">
    @@ -230,13 +247,41 @@
             another map.
             Default value is <code>15000</code> milliseconds.
           </attribute>
    +      <attribute name="sessionAttributeNameFilter" required="false">
    +        <p>A regular expression used to filter which session attributes will be
    +        replicated. An attribute will only be replicated if its name matches
    +        this pattern. If the pattern is zero length or <code>null</code>, all
    +        attributes are eligible for replication. The pattern is anchored so the
    +        session attribute name must fully match the pattern. As an example, the
    +        value <code>(userName|sessionHistory)</code> will only replicate the
    +        two session attributes named <code>userName</code> and
    +        <code>sessionHistory</code>. If not specified, the default value of
    +        <code>null</code> will be used.</p>
    +      </attribute>
    +      <attribute name="sessionAttributeValueClassNameFilter" required="false">
    +        <p>A regular expression used to filter which session attributes will be
    +        replicated. An attribute will only be replicated if the implementation
    +        class name of the value matches this pattern. If the pattern is zero
    +        length or <code>null</code>, all attributes are eligible for
    +        replication. The pattern is anchored so the fully qualified class name
    +        must fully match the pattern. If not specified, the default value of
    +        <code>null</code> will be used.</p>
    +      </attribute>
           <attribute name="terminateOnStartFailure" required="false">
             Set to true if you wish to terminate replication map when replication
             map fails to start. If replication map is terminated, associated context
             will fail to start. If you set this attribute to false, replication map
             does not end. It will try to join the map membership in the heartbeat.
             Default value is <code>false</code> .
           </attribute>
    +      <attribute name="warnOnSessionAttributeFilterFailure" required="false">
    +        <p>If <strong>sessionAttributeNameFilter</strong> or
    +        <strong>sessionAttributeValueClassNameFilter</strong> blocks an
    +        attribute, should this be logged at <code>WARN</code> level? If
    +        <code>WARN</code> level logging is disabled then it will be logged at
    +        <code>DEBUG</code>. The default value of this attribute is
    +        <code>false</code>.</p>
    +      </attribute>
         </attributes>
       </subsection>
     </section>
    
  • webapps/docs/config/manager.xml+42 4 modified
    @@ -176,15 +176,34 @@
           </attribute>
     
           <attribute name="sessionAttributeNameFilter" required="false">
    -        A regular expression used to filter which session attributes will be
    +        <p>A regular expression used to filter which session attributes will be
             distributed. An attribute will only be distributed if its name matches
             this pattern. If the pattern is zero length or <code>null</code>, all
             attributes are eligible for distribution. The pattern is anchored so the
             session attribute name must fully match the pattern. As an example, the
             value <code>(userName|sessionHistory)</code> will only distribute the
             two session attributes named <code>userName</code> and
             <code>sessionHistory</code>. If not specified, the default value of
    -        <code>null</code> will be used.
    +        <code>null</code> will be used.</p>
    +      </attribute>
    +
    +      <attribute name="sessionAttributeValueClassNameFilter" required="false">
    +        <p>A regular expression used to filter which session attributes will be
    +        distributed. An attribute will only be distributed if the implementation
    +        class name of the value matches this pattern. If the pattern is zero
    +        length or <code>null</code>, all attributes are eligible for
    +        distribution. The pattern is anchored so the fully qualified class name
    +        must fully match the pattern. If not specified, the default value of
    +        <code>null</code> will be used.</p>
    +      </attribute>
    +
    +      <attribute name="warnOnSessionAttributeFilterFailure" required="false">
    +        <p>If <strong>sessionAttributeNameFilter</strong> or
    +        <strong>sessionAttributeValueClassNameFilter</strong> blocks an
    +        attribute, should this be logged at <code>WARN</code> level? If
    +        <code>WARN</code> level logging is disabled then it will be logged at
    +        <code>DEBUG</code>. The default value of this attribute is
    +        <code>false</code>.</p>
           </attribute>
         </attributes>
     
    @@ -277,15 +296,34 @@
           </attribute>
     
           <attribute name="sessionAttributeNameFilter" required="false">
    -        A regular expression used to filter which session attributes will be
    +        <p>A regular expression used to filter which session attributes will be
             distributed. An attribute will only be distributed if its name matches
             this pattern. If the pattern is zero length or <code>null</code>, all
             attributes are eligible for distribution. The pattern is anchored so the
             session attribute name must fully match the pattern. As an example, the
             value <code>(userName|sessionHistory)</code> will only distribute the
             two session attributes named <code>userName</code> and
             <code>sessionHistory</code>. If not specified, the default value of
    -        <code>null</code> will be used.
    +        <code>null</code> will be used.</p>
    +      </attribute>
    +
    +      <attribute name="sessionAttributeValueClassNameFilter" required="false">
    +        <p>A regular expression used to filter which session attributes will be
    +        distributed. An attribute will only be distributed if the implementation
    +        class name of the value matches this pattern. If the pattern is zero
    +        length or <code>null</code>, all attributes are eligible for
    +        distribution. The pattern is anchored so the fully qualified class name
    +        must fully match the pattern. If not specified, the default value of
    +        <code>null</code> will be used.</p>
    +      </attribute>
    +
    +      <attribute name="warnOnSessionAttributeFilterFailure" required="false">
    +        <p>If <strong>sessionAttributeNameFilter</strong> or
    +        <strong>sessionAttributeValueClassNameFilter</strong> blocks an
    +        attribute, should this be logged at <code>WARN</code> level? If
    +        <code>WARN</code> level logging is disabled then it will be logged at
    +        <code>DEBUG</code>. The default value of this attribute is
    +        <code>false</code>.</p>
           </attribute>
         </attributes>
     
    
2e5cc28052e8

When using the new sessionAttributeValueClassNameFilter, apply the filter earlier rather than loading the class and then deciding to filter it out.

https://github.com/apache/tomcat80Mark ThomasJan 22, 2016via ghsa
8 files changed · +154 15
  • java/org/apache/catalina/session/ManagerBase.java+25 1 modified
    @@ -37,6 +37,7 @@
     import org.apache.catalina.Container;
     import org.apache.catalina.Context;
     import org.apache.catalina.Engine;
    +import org.apache.catalina.Globals;
     import org.apache.catalina.Lifecycle;
     import org.apache.catalina.LifecycleException;
     import org.apache.catalina.LifecycleState;
    @@ -226,8 +227,31 @@ public abstract class ManagerBase extends LifecycleMBeanBase implements Manager
         private boolean warnOnSessionAttributeFilterFailure;
     
     
    -    // ------------------------------------------------------------- Properties
    +    // ------------------------------------------------------------ Constructors
     
    +    public ManagerBase() {
    +        if (Globals.IS_SECURITY_ENABLED) {
    +            // Minimum set required for default distribution/persistence to work
    +            // plus String
    +            setSessionAttributeValueClassNameFilter(
    +                    "java\\.lang\\.(?:Boolean|Integer|Long|Number|String)");
    +            setWarnOnSessionAttributeFilterFailure(true);
    +        }
    +    }
    +
    +
    +    // -------------------------------------------------------------- Properties
    +
    +    /**
    +     * Obtain the regular expression used to filter session attribute based on
    +     * attribute name. The regular expression is anchored so it must match the
    +     * entire name
    +     *
    +     * @return The regular expression currently used to filter attribute names.
    +     *         {@code null} means no filter is applied. If an empty string is
    +     *         specified then no names will match the filter and all attributes
    +     *         will be blocked.
    +     */
         public String getSessionAttributeNameFilter() {
             if (sessionAttributeNamePattern == null) {
                 return null;
    
  • java/org/apache/catalina/session/StandardManager.java+6 2 modified
    @@ -191,10 +191,12 @@ protected void doLoad() throws ClassNotFoundException, IOException {
             }
             Loader loader = null;
             ClassLoader classLoader = null;
    +        Log logger = null;
             try (FileInputStream fis = new FileInputStream(file.getAbsolutePath());
    -                BufferedInputStream bis = new BufferedInputStream(fis);) {
    +                BufferedInputStream bis = new BufferedInputStream(fis)) {
                 Context c = getContext();
                 loader = c.getLoader();
    +            logger = c.getLogger();
                 if (loader != null) {
                     classLoader = loader.getClassLoader();
                 }
    @@ -204,7 +206,9 @@ protected void doLoad() throws ClassNotFoundException, IOException {
     
                 // Load the previously unloaded active sessions
                 synchronized (sessions) {
    -                try (ObjectInputStream ois = new CustomObjectInputStream(bis, classLoader)) {
    +                try (ObjectInputStream ois = new CustomObjectInputStream(bis, classLoader, logger,
    +                        getSessionAttributeValueClassNamePattern(),
    +                        getWarnOnSessionAttributeFilterFailure())) {
                         Integer count = (Integer) ois.readObject();
                         int n = count.intValue();
                         if (log.isDebugEnabled())
    
  • java/org/apache/catalina/session/StoreBase.java+14 1 modified
    @@ -215,7 +215,20 @@ public void processExpires() {
          */
         protected ObjectInputStream getObjectInputStream(InputStream is) throws IOException {
             BufferedInputStream bis = new BufferedInputStream(is);
    -        return new CustomObjectInputStream(bis, Thread.currentThread().getContextClassLoader());
    +
    +        CustomObjectInputStream ois;
    +        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    +
    +        if (manager instanceof ManagerBase) {
    +            ManagerBase managerBase = (ManagerBase) manager;
    +            ois = new CustomObjectInputStream(bis, classLoader, manager.getContext().getLogger(),
    +                    managerBase.getSessionAttributeValueClassNamePattern(),
    +                    managerBase.getWarnOnSessionAttributeFilterFailure());
    +        } else {
    +            ois = new CustomObjectInputStream(bis, classLoader);
    +        }
    +
    +        return ois;
         }
     
     
    
  • java/org/apache/catalina/util/CustomObjectInputStream.java+85 2 modified
    @@ -18,9 +18,18 @@
     
     import java.io.IOException;
     import java.io.InputStream;
    +import java.io.InvalidClassException;
     import java.io.ObjectInputStream;
     import java.io.ObjectStreamClass;
     import java.lang.reflect.Proxy;
    +import java.util.Collections;
    +import java.util.Set;
    +import java.util.WeakHashMap;
    +import java.util.concurrent.ConcurrentHashMap;
    +import java.util.regex.Pattern;
    +
    +import org.apache.juli.logging.Log;
    +import org.apache.tomcat.util.res.StringManager;
     
     /**
      * Custom subclass of <code>ObjectInputStream</code> that loads from the
    @@ -32,23 +41,81 @@
      */
     public final class CustomObjectInputStream extends ObjectInputStream {
     
    +    private static final StringManager sm = StringManager.getManager(CustomObjectInputStream.class);
    +
    +    private static final WeakHashMap<ClassLoader, Set<String>> reportedClassCache =
    +            new WeakHashMap<>();
    +
         /**
          * The class loader we will use to resolve classes.
          */
         private final ClassLoader classLoader;
    +    private final Set<String> reportedClasses;
    +    private final Log log;
    +
    +    private final Pattern allowedClassNamePattern;
    +    private final String allowedClassNameFilter;
    +    private final boolean warnOnFailure;
     
     
         /**
    -     * Construct a new instance of CustomObjectInputStream
    +     * Construct a new instance of CustomObjectInputStream without any filtering
    +     * of deserialized classes.
          *
          * @param stream The input stream we will read from
          * @param classLoader The class loader used to instantiate objects
          *
          * @exception IOException if an input/output error occurs
          */
         public CustomObjectInputStream(InputStream stream, ClassLoader classLoader) throws IOException {
    +        this(stream, classLoader, null, null, false);
    +    }
    +
    +
    +    /**
    +     * Construct a new instance of CustomObjectInputStream with filtering of
    +     * deserialized classes.
    +     *
    +     * @param stream The input stream we will read from
    +     * @param classLoader The class loader used to instantiate objects
    +     * @param log The logger to use to report any issues. It may only be null if
    +     *            the filterMode does not require logging
    +     * @param allowedClassNamePattern The regular expression to use to filter
    +     *                                deserialized classes. The fully qualified
    +     *                                class name must match this pattern for
    +     *                                deserialization to be allowed if filtering
    +     *                                is enabled.
    +     * @param warnOnFailure Should any failures be logged?
    +     *
    +     * @exception IOException if an input/output error occurs
    +     */
    +    public CustomObjectInputStream(InputStream stream, ClassLoader classLoader,
    +            Log log, Pattern allowedClassNamePattern, boolean warnOnFailure)
    +            throws IOException {
             super(stream);
    +        if (log == null && allowedClassNamePattern != null && warnOnFailure) {
    +            throw new IllegalArgumentException(
    +                    sm.getString("customObjectInputStream.logRequired"));
    +        }
             this.classLoader = classLoader;
    +        this.log = log;
    +        this.allowedClassNamePattern = allowedClassNamePattern;
    +        if (allowedClassNamePattern == null) {
    +            this.allowedClassNameFilter = null;
    +        } else {
    +            this.allowedClassNameFilter = allowedClassNamePattern.toString();
    +        }
    +        this.warnOnFailure = warnOnFailure;
    +
    +        Set<String> reportedClasses;
    +        synchronized (reportedClassCache) {
    +            reportedClasses = reportedClassCache.get(classLoader);
    +            if (reportedClasses == null) {
    +                reportedClasses = Collections.newSetFromMap(new ConcurrentHashMap<String,Boolean>());
    +                reportedClassCache.put(classLoader, reportedClasses);
    +            }
    +        }
    +        this.reportedClasses = reportedClasses;
         }
     
     
    @@ -64,8 +131,24 @@ public CustomObjectInputStream(InputStream stream, ClassLoader classLoader) thro
         @Override
         public Class<?> resolveClass(ObjectStreamClass classDesc)
             throws ClassNotFoundException, IOException {
    +
    +        String name = classDesc.getName();
    +        if (allowedClassNamePattern != null) {
    +            boolean allowed = allowedClassNamePattern.matcher(name).matches();
    +            if (!allowed) {
    +                boolean doLog = warnOnFailure && reportedClasses.add(name);
    +                String msg = sm.getString("customObjectInputStream.nomatch", name, allowedClassNameFilter);
    +                if (doLog) {
    +                    log.warn(msg);
    +                } else if (log.isDebugEnabled()) {
    +                    log.debug(msg);
    +                }
    +                throw new InvalidClassException(msg);
    +            }
    +        }
    +
             try {
    -            return Class.forName(classDesc.getName(), false, classLoader);
    +            return Class.forName(name, false, classLoader);
             } catch (ClassNotFoundException e) {
                 try {
                     // Try also the superclass because of primitive types
    
  • java/org/apache/catalina/util/LocalStrings.properties+2 0 modified
    @@ -17,6 +17,8 @@ parameterMap.locked=No modifications are allowed to a locked ParameterMap
     resourceSet.locked=No modifications are allowed to a locked ResourceSet
     hexUtil.bad=Bad hexadecimal digit
     hexUtil.odd=Odd number of hexadecimal digits
    +customObjectInputStream.logRequired=A valid logger is required for class name filtering with logging
    +customObjectInputStream.nomatch=The class [{0}] did not match the regular expression [{1}] for classes allowed to be deserialized
     #Default Messages Utilized by the ExtensionValidator
     extensionValidator.web-application-manifest=Web Application Manifest
     extensionValidator.extension-not-found-error=ExtensionValidator[{0}][{1}]: Required extension [{2}] not found.
    
  • webapps/docs/changelog.xml+2 1 modified
    @@ -166,7 +166,8 @@
             based on the implementation class of the value and optional
             <code>WARN</code> level logging if an attribute is filtered. These
             options are avaialble for all of the Manager implementations that ship
    -        with Tomcat. (markt)
    +        with Tomcat. When a <code>SecurityManager</code> is used filtering will
    +        be enabled by default. (markt)
           </add>
         </changelog>
       </subsection>
    
  • webapps/docs/config/cluster-manager.xml+10 4 modified
    @@ -198,7 +198,9 @@
             length or <code>null</code>, all attributes are eligible for
             replication. The pattern is anchored so the fully qualified class name
             must fully match the pattern. If not specified, the default value of
    -        <code>null</code> will be used.</p>
    +        <code>null</code> will be used unless a <code>SecurityManager</code> is
    +        enabled in which case the default will be
    +        <code>java\\.lang\\.(?:Boolean|Integer|Long|Number|String)</code>.</p>
           </attribute>
           <attribute name="stateTimestampDrop" required="false">
             When this node sends a <code>GET_ALL_SESSIONS</code> message to other
    @@ -217,7 +219,8 @@
             attribute, should this be logged at <code>WARN</code> level? If
             <code>WARN</code> level logging is disabled then it will be logged at
             <code>DEBUG</code>. The default value of this attribute is
    -        <code>false</code>.</p>
    +        <code>false</code> unless a <code>SecurityManager</code> is enabled in
    +        which case the default will be <code>true</code>.</p>
           </attribute>
         </attributes>
       </subsection>
    @@ -260,7 +263,9 @@
             length or <code>null</code>, all attributes are eligible for
             replication. The pattern is anchored so the fully qualified class name
             must fully match the pattern. If not specified, the default value of
    -        <code>null</code> will be used.</p>
    +        <code>null</code> will be used unless a <code>SecurityManager</code> is
    +        enabled in which case the default will be
    +        <code>java\\.lang\\.(?:Boolean|Integer|Long|Number|String)</code>.</p>
           </attribute>
           <attribute name="terminateOnStartFailure" required="false">
             Set to true if you wish to terminate replication map when replication
    @@ -275,7 +280,8 @@
             attribute, should this be logged at <code>WARN</code> level? If
             <code>WARN</code> level logging is disabled then it will be logged at
             <code>DEBUG</code>. The default value of this attribute is
    -        <code>false</code>.</p>
    +        <code>false</code> unless a <code>SecurityManager</code> is enabled in
    +        which case the default will be <code>true</code>.</p>
           </attribute>
         </attributes>
       </subsection>
    
  • webapps/docs/config/manager.xml+10 4 modified
    @@ -198,7 +198,9 @@
             length or <code>null</code>, all attributes are eligible for
             distribution. The pattern is anchored so the fully qualified class name
             must fully match the pattern. If not specified, the default value of
    -        <code>null</code> will be used.</p>
    +        <code>null</code> will be used unless a <code>SecurityManager</code> is
    +        enabled in which case the default will be
    +        <code>java\\.lang\\.(?:Boolean|Integer|Long|Number|String)</code>.</p>
           </attribute>
     
           <attribute name="warnOnSessionAttributeFilterFailure" required="false">
    @@ -207,7 +209,8 @@
             attribute, should this be logged at <code>WARN</code> level? If
             <code>WARN</code> level logging is disabled then it will be logged at
             <code>DEBUG</code>. The default value of this attribute is
    -        <code>false</code>.</p>
    +        <code>false</code> unless a <code>SecurityManager</code> is enabled in
    +        which case the default will be <code>true</code>.</p>
           </attribute>
         </attributes>
     
    @@ -318,7 +321,9 @@
             length or <code>null</code>, all attributes are eligible for
             distribution. The pattern is anchored so the fully qualified class name
             must fully match the pattern. If not specified, the default value of
    -        <code>null</code> will be used.</p>
    +        <code>null</code> will be used unless a <code>SecurityManager</code> is
    +        enabled in which case the default will be
    +        <code>java\\.lang\\.(?:Boolean|Integer|Long|Number|String)</code>.</p>
           </attribute>
     
           <attribute name="warnOnSessionAttributeFilterFailure" required="false">
    @@ -327,7 +332,8 @@
             attribute, should this be logged at <code>WARN</code> level? If
             <code>WARN</code> level logging is disabled then it will be logged at
             <code>DEBUG</code>. The default value of this attribute is
    -        <code>false</code>.</p>
    +        <code>false</code> unless a <code>SecurityManager</code> is enabled in
    +        which case the default will be <code>true</code>.</p>
           </attribute>
         </attributes>
     
    
5430f30c7938

Expand the session attribute filtering options

https://github.com/apache/tomcat80Mark ThomasJan 22, 2016via ghsa
8 files changed · +259 18
  • java/org/apache/catalina/ha/session/ClusterManagerBase.java+3 0 modified
    @@ -192,6 +192,9 @@ protected void clone(ClusterManagerBase copy) {
             copy.setProcessExpiresFrequency(getProcessExpiresFrequency());
             copy.setNotifyListenersOnReplication(isNotifyListenersOnReplication());
             copy.setSessionAttributeFilter(getSessionAttributeFilter());
    +        copy.setSessionAttributeNameFilter(getSessionAttributeNameFilter());
    +        copy.setSessionAttributeValueClassNameFilter(getSessionAttributeValueClassNameFilter());
    +        copy.setWarnOnSessionAttributeFilterFailure(getWarnOnSessionAttributeFilterFailure());
             copy.setSecureRandomClass(getSecureRandomClass());
             copy.setSecureRandomProvider(getSecureRandomProvider());
             copy.setSecureRandomAlgorithm(getSecureRandomAlgorithm());
    
  • java/org/apache/catalina/ha/session/mbeans-descriptors.xml+16 0 modified
    @@ -318,6 +318,14 @@
           name="sessionAttributeNameFilter"
           descritpion="The string pattern used for including session attributes in replication. Null means all attributes are included."
           type="java.lang.String"/>
    +    <attribute
    +      name="sessionAttributeValueClassNameFilter"
    +      description="The regular expression used to filter session attributes based on the implementation class of the value. The regular expression is anchored and must match the fully qualified class name."
    +      type="java.lang.String"/>
    +    <attribute
    +      name="warnOnSessionAttributeFilterFailure"
    +      description="Should a WARN level log message be generated if a session attribute fails to match sessionAttributeNameFilter or sessionAttributeClassNameFilter?"
    +      type="boolean"/>
         <operation
           name="expireSession"
           description="Expired the given session"
    @@ -544,6 +552,14 @@
           name="sessionAttributeNameFilter"
           descritpion="The string pattern used for including session attributes in replication. Null means all attributes are included."
           type="java.lang.String"/>
    +    <attribute
    +      name="sessionAttributeValueClassNameFilter"
    +      description="The regular expression used to filter session attributes based on the implementation class of the value. The regular expression is anchored and must match the fully qualified class name."
    +      type="java.lang.String"/>
    +    <attribute
    +      name="warnOnSessionAttributeFilterFailure"
    +      description="Should a WARN level log message be generated if a session attribute fails to match sessionAttributeNameFilter or sessionAttributeClassNameFilter?"
    +      type="boolean"/>
         <operation
           name="expireSession"
           description="Expired the given session"
    
  • java/org/apache/catalina/session/LocalStrings.properties+2 0 modified
    @@ -33,6 +33,8 @@ JDBCStore.commitSQLException=SQLException committing connection before closing
     managerBase.container.noop=Managers added to containers other than Contexts will never be used
     managerBase.contextNull=The Context must be set to a non-null value before the Manager is used
     managerBase.createSession.ise=createSession: Too many active sessions
    +managerBase.sessionAttributeNameFilter=Skipped session attribute named [{0}] because it did not match the name filter [{1}]
    +managerBase.sessionAttributeValueClassNameFilter=Skipped session attribute named [{0}] because the value type [{1}] did not match the filter [{2}]
     managerBase.sessionTimeout=Invalid session timeout setting {0}
     managerBase.setContextNotNew=It is illegal to call setContext() to change the Context associated with a Manager if the Manager is not in the NEW state
     standardManager.loading=Loading persisted sessions from {0}
    
  • java/org/apache/catalina/session/ManagerBase.java+117 3 modified
    @@ -33,6 +33,7 @@
     import java.util.concurrent.ConcurrentHashMap;
     import java.util.concurrent.atomic.AtomicLong;
     import java.util.regex.Pattern;
    +import java.util.regex.PatternSyntaxException;
     
     import org.apache.catalina.Container;
     import org.apache.catalina.Context;
    @@ -214,6 +215,10 @@ public abstract class ManagerBase extends LifecycleMBeanBase
     
         private Pattern sessionAttributeNamePattern;
     
    +    private Pattern sessionAttributeValueClassNamePattern;
    +
    +    private boolean warnOnSessionAttributeFilterFailure;
    +
     
         // ------------------------------------------------------------- Properties
     
    @@ -238,6 +243,86 @@ protected Pattern getSessionAttributeNamePattern() {
         }
     
     
    +    /**
    +     * Obtain the regular expression used to filter session attribute based on
    +     * the implementation class of the value. The regular expression is anchored
    +     * and must match the fully qualified class name.
    +     *
    +     * @return The regular expression currently used to filter class names.
    +     *         {@code null} means no filter is applied. If an empty string is
    +     *         specified then no names will match the filter and all attributes
    +     *         will be blocked.
    +     */
    +    public String getSessionAttributeValueClassNameFilter() {
    +        if (sessionAttributeValueClassNamePattern == null) {
    +            return null;
    +        }
    +        return sessionAttributeValueClassNamePattern.toString();
    +    }
    +
    +
    +    /**
    +     * Provides {@link #getSessionAttributeValueClassNameFilter()} as a
    +     * pre-compiled regular expression pattern.
    +     *
    +     * @return The pre-compiled pattern used to filter session attributes based
    +     *         on the implementation class name of the value. {@code null} means
    +     *         no filter is applied.
    +     */
    +    protected Pattern getSessionAttributeValueClassNamePattern() {
    +        return sessionAttributeValueClassNamePattern;
    +    }
    +
    +
    +    /**
    +     * Set the regular expression to use to filter classes used for session
    +     * attributes. The regular expression is anchored and must match the fully
    +     * qualified class name.
    +     *
    +     * @param sessionAttributeValueClassNameFilter The regular expression to use
    +     *            to filter session attributes based on class name. Use {@code
    +     *            null} if no filtering is required. If an empty string is
    +     *           specified then no names will match the filter and all
    +     *           attributes will be blocked.
    +     *
    +     * @throws PatternSyntaxException If the expression is not valid
    +     */
    +    public void setSessionAttributeValueClassNameFilter(String sessionAttributeValueClassNameFilter)
    +            throws PatternSyntaxException {
    +        if (sessionAttributeValueClassNameFilter == null ||
    +                sessionAttributeValueClassNameFilter.length() == 0) {
    +            sessionAttributeValueClassNamePattern = null;
    +        }
    +        sessionAttributeValueClassNamePattern =
    +                Pattern.compile(sessionAttributeValueClassNameFilter);
    +    }
    +
    +
    +    /**
    +     * Should a warn level log message be generated if a session attribute is
    +     * not persisted / replicated / restored.
    +     *
    +     * @return {@code true} if a warn level log message should be generated
    +     */
    +    public boolean getWarnOnSessionAttributeFilterFailure() {
    +        return warnOnSessionAttributeFilterFailure;
    +    }
    +
    +
    +    /**
    +     * Configure whether or not a warn level log message should be generated if
    +     * a session attribute is not persisted / replicated / restored.
    +     *
    +     * @param warnOnSessionAttributeFilterFailure {@code true} if the
    +     *            warn level message should be generated
    +     *
    +     */
    +    public void setWarnOnSessionAttributeFilterFailure(
    +            boolean warnOnSessionAttributeFilterFailure) {
    +        this.warnOnSessionAttributeFilterFailure = warnOnSessionAttributeFilterFailure;
    +    }
    +
    +
         @Override
         @Deprecated
         public Container getContainer() {
    @@ -784,10 +869,39 @@ protected void changeSessionId(Session session, String newId,
         @Override
         public boolean willAttributeDistribute(String name, Object value) {
             Pattern sessionAttributeNamePattern = getSessionAttributeNamePattern();
    -        if (sessionAttributeNamePattern == null) {
    -            return true;
    +        if (sessionAttributeNamePattern != null) {
    +            if (!sessionAttributeNamePattern.matcher(name).matches()) {
    +                if (getWarnOnSessionAttributeFilterFailure() || log.isDebugEnabled()) {
    +                    String msg = sm.getString("managerBase.sessionAttributeNameFilter",
    +                            name, sessionAttributeNamePattern);
    +                    if (getWarnOnSessionAttributeFilterFailure()) {
    +                        log.warn(msg);
    +                    } else {
    +                        log.debug(msg);
    +                    }
    +                }
    +                return false;
    +            }
             }
    -        return sessionAttributeNamePattern.matcher(name).matches();
    +
    +        Pattern sessionAttributeValueClassNamePattern = getSessionAttributeValueClassNamePattern();
    +        if (value != null && sessionAttributeValueClassNamePattern != null) {
    +            if (!sessionAttributeValueClassNamePattern.matcher(
    +                    value.getClass().getName()).matches()) {
    +                if (getWarnOnSessionAttributeFilterFailure() || log.isDebugEnabled()) {
    +                    String msg = sm.getString("managerBase.sessionAttributeValueClassNameFilter",
    +                            name, value.getClass().getName(), sessionAttributeNamePattern);
    +                    if (getWarnOnSessionAttributeFilterFailure()) {
    +                        log.warn(msg);
    +                    } else {
    +                        log.debug(msg);
    +                    }
    +                }
    +                return false;
    +            }
    +        }
    +
    +        return true;
         }
     
     
    
  • java/org/apache/catalina/session/mbeans-descriptors.xml+16 0 modified
    @@ -137,6 +137,14 @@
               descritpion="The string pattern used for including session attributes in distribution. Null means all attributes are included."
                      type="java.lang.String"/>
     
    +    <attribute   name="sessionAttributeValueClassNameFilter"
    +          description="The regular expression used to filter session attributes based on the implementation class of the value. The regular expression is anchored and must match the fully qualified class name."
    +                 type="java.lang.String"/>
    +
    +    <attribute   name="warnOnSessionAttributeFilterFailure"
    +          description="Should a WARN level log message be generated if a session attribute fails to match sessionAttributeNameFilter or sessionAttributeClassNameFilter?"
    +                 type="boolean"/>
    +
         <operation   name="backgroundProcess"
               description="Invalidate all sessions that have expired."
                    impact="ACTION"
    @@ -329,6 +337,14 @@
               descritpion="The string pattern used for including session attributes in distribution. Null means all attributes are included."
                      type="java.lang.String"/>
     
    +    <attribute   name="sessionAttributeValueClassNameFilter"
    +          description="The regular expression used to filter session attributes based on the implementation class of the value. The regular expression is anchored and must match the fully qualified class name."
    +                 type="java.lang.String"/>
    +
    +    <attribute   name="warnOnSessionAttributeFilterFailure"
    +          description="Should a WARN level log message be generated if a session attribute fails to match sessionAttributeNameFilter or sessionAttributeClassNameFilter?"
    +                 type="boolean"/>
    +
         <operation   name="backgroundProcess"
               description="Invalidate all sessions that have expired."
                    impact="ACTION"
    
  • webapps/docs/changelog.xml+7 0 modified
    @@ -161,6 +161,13 @@
             well as unload to ensure that configuration changes made while the web
             application is stopped are applied to any persisted data. (markt)
           </add>
    +      <add>
    +        Extend the session attribute filtering options to include filtering
    +        based on the implementation class of the value and optional
    +        <code>WARN</code> level logging if an attribute is filtered. These
    +        options are avaialble for all of the Manager implementations that ship
    +        with Tomcat. (markt)
    +      </add>
         </changelog>
       </subsection>
       <subsection name="Jasper">
    
  • webapps/docs/config/cluster-manager.xml+56 11 modified
    @@ -77,17 +77,6 @@
             when session attributes are being replicated or removed across Tomcat
             nodes in the cluster.
           </attribute>
    -      <attribute name="sessionAttributeNameFilter" required="false">
    -        A regular expression used to filter which session attributes will be
    -        replicated. An attribute will only be replicated if its name matches
    -        this pattern. If the pattern is zero length or <code>null</code>, all
    -        attributes are eligible for replication. The pattern is anchored so the
    -        session attribute name must fully match the pattern. As an example, the
    -        value <code>(userName|sessionHistory)</code> will only replicate the
    -        two session attributes named <code>userName</code> and
    -        <code>sessionHistory</code>. If not specified, the default value of
    -        <code>null</code> will be used.
    -      </attribute>
           <attribute name="maxInactiveInterval" required="false">
             <p>The initial maximum time interval, in seconds,
             between client requests before a session is invalidated. A negative value
    @@ -191,6 +180,26 @@
             effective only when <code>sendAllSessions</code> is <code>false</code>.
             Default is <code>2000</code> milliseconds.
           </attribute>
    +      <attribute name="sessionAttributeNameFilter" required="false">
    +        <p>A regular expression used to filter which session attributes will be
    +        replicated. An attribute will only be replicated if its name matches
    +        this pattern. If the pattern is zero length or <code>null</code>, all
    +        attributes are eligible for replication. The pattern is anchored so the
    +        session attribute name must fully match the pattern. As an example, the
    +        value <code>(userName|sessionHistory)</code> will only replicate the
    +        two session attributes named <code>userName</code> and
    +        <code>sessionHistory</code>. If not specified, the default value of
    +        <code>null</code> will be used.</p>
    +      </attribute>
    +      <attribute name="sessionAttributeValueClassNameFilter" required="false">
    +        <p>A regular expression used to filter which session attributes will be
    +        replicated. An attribute will only be replicated if the implementation
    +        class name of the value matches this pattern. If the pattern is zero
    +        length or <code>null</code>, all attributes are eligible for
    +        replication. The pattern is anchored so the fully qualified class name
    +        must fully match the pattern. If not specified, the default value of
    +        <code>null</code> will be used.</p>
    +      </attribute>
           <attribute name="stateTimestampDrop" required="false">
             When this node sends a <code>GET_ALL_SESSIONS</code> message to other
             node, all session messages that are received as a response are queued.
    @@ -202,6 +211,14 @@
             If set to <code>false</code>, all queued session messages are handled.
             Default is <code>true</code>.
           </attribute>
    +      <attribute name="warnOnSessionAttributeFilterFailure" required="false">
    +        <p>If <strong>sessionAttributeNameFilter</strong> or
    +        <strong>sessionAttributeValueClassNameFilter</strong> blocks an
    +        attribute, should this be logged at <code>WARN</code> level? If
    +        <code>WARN</code> level logging is disabled then it will be logged at
    +        <code>DEBUG</code>. The default value of this attribute is
    +        <code>false</code>.</p>
    +      </attribute>
         </attributes>
       </subsection>
       <subsection name="org.apache.catalina.ha.session.BackupManager Attributes">
    @@ -225,13 +242,41 @@
             another map.
             Default value is <code>15000</code> milliseconds.
           </attribute>
    +      <attribute name="sessionAttributeNameFilter" required="false">
    +        <p>A regular expression used to filter which session attributes will be
    +        replicated. An attribute will only be replicated if its name matches
    +        this pattern. If the pattern is zero length or <code>null</code>, all
    +        attributes are eligible for replication. The pattern is anchored so the
    +        session attribute name must fully match the pattern. As an example, the
    +        value <code>(userName|sessionHistory)</code> will only replicate the
    +        two session attributes named <code>userName</code> and
    +        <code>sessionHistory</code>. If not specified, the default value of
    +        <code>null</code> will be used.</p>
    +      </attribute>
    +      <attribute name="sessionAttributeValueClassNameFilter" required="false">
    +        <p>A regular expression used to filter which session attributes will be
    +        replicated. An attribute will only be replicated if the implementation
    +        class name of the value matches this pattern. If the pattern is zero
    +        length or <code>null</code>, all attributes are eligible for
    +        replication. The pattern is anchored so the fully qualified class name
    +        must fully match the pattern. If not specified, the default value of
    +        <code>null</code> will be used.</p>
    +      </attribute>
           <attribute name="terminateOnStartFailure" required="false">
             Set to true if you wish to terminate replication map when replication
             map fails to start. If replication map is terminated, associated context
             will fail to start. If you set this attribute to false, replication map
             does not end. It will try to join the map membership in the heartbeat.
             Default value is <code>false</code> .
           </attribute>
    +      <attribute name="warnOnSessionAttributeFilterFailure" required="false">
    +        <p>If <strong>sessionAttributeNameFilter</strong> or
    +        <strong>sessionAttributeValueClassNameFilter</strong> blocks an
    +        attribute, should this be logged at <code>WARN</code> level? If
    +        <code>WARN</code> level logging is disabled then it will be logged at
    +        <code>DEBUG</code>. The default value of this attribute is
    +        <code>false</code>.</p>
    +      </attribute>
         </attributes>
       </subsection>
     </section>
    
  • webapps/docs/config/manager.xml+42 4 modified
    @@ -176,15 +176,34 @@
           </attribute>
     
           <attribute name="sessionAttributeNameFilter" required="false">
    -        A regular expression used to filter which session attributes will be
    +        <p>A regular expression used to filter which session attributes will be
             distributed. An attribute will only be distributed if its name matches
             this pattern. If the pattern is zero length or <code>null</code>, all
             attributes are eligible for distribution. The pattern is anchored so the
             session attribute name must fully match the pattern. As an example, the
             value <code>(userName|sessionHistory)</code> will only distribute the
             two session attributes named <code>userName</code> and
             <code>sessionHistory</code>. If not specified, the default value of
    -        <code>null</code> will be used.
    +        <code>null</code> will be used.</p>
    +      </attribute>
    +
    +      <attribute name="sessionAttributeValueClassNameFilter" required="false">
    +        <p>A regular expression used to filter which session attributes will be
    +        distributed. An attribute will only be distributed if the implementation
    +        class name of the value matches this pattern. If the pattern is zero
    +        length or <code>null</code>, all attributes are eligible for
    +        distribution. The pattern is anchored so the fully qualified class name
    +        must fully match the pattern. If not specified, the default value of
    +        <code>null</code> will be used.</p>
    +      </attribute>
    +
    +      <attribute name="warnOnSessionAttributeFilterFailure" required="false">
    +        <p>If <strong>sessionAttributeNameFilter</strong> or
    +        <strong>sessionAttributeValueClassNameFilter</strong> blocks an
    +        attribute, should this be logged at <code>WARN</code> level? If
    +        <code>WARN</code> level logging is disabled then it will be logged at
    +        <code>DEBUG</code>. The default value of this attribute is
    +        <code>false</code>.</p>
           </attribute>
         </attributes>
     
    @@ -277,15 +296,34 @@
           </attribute>
     
           <attribute name="sessionAttributeNameFilter" required="false">
    -        A regular expression used to filter which session attributes will be
    +        <p>A regular expression used to filter which session attributes will be
             distributed. An attribute will only be distributed if its name matches
             this pattern. If the pattern is zero length or <code>null</code>, all
             attributes are eligible for distribution. The pattern is anchored so the
             session attribute name must fully match the pattern. As an example, the
             value <code>(userName|sessionHistory)</code> will only distribute the
             two session attributes named <code>userName</code> and
             <code>sessionHistory</code>. If not specified, the default value of
    -        <code>null</code> will be used.
    +        <code>null</code> will be used.</p>
    +      </attribute>
    +
    +      <attribute name="sessionAttributeValueClassNameFilter" required="false">
    +        <p>A regular expression used to filter which session attributes will be
    +        distributed. An attribute will only be distributed if the implementation
    +        class name of the value matches this pattern. If the pattern is zero
    +        length or <code>null</code>, all attributes are eligible for
    +        distribution. The pattern is anchored so the fully qualified class name
    +        must fully match the pattern. If not specified, the default value of
    +        <code>null</code> will be used.</p>
    +      </attribute>
    +
    +      <attribute name="warnOnSessionAttributeFilterFailure" required="false">
    +        <p>If <strong>sessionAttributeNameFilter</strong> or
    +        <strong>sessionAttributeValueClassNameFilter</strong> blocks an
    +        attribute, should this be logged at <code>WARN</code> level? If
    +        <code>WARN</code> level logging is disabled then it will be logged at
    +        <code>DEBUG</code>. The default value of this attribute is
    +        <code>false</code>.</p>
           </attribute>
         </attributes>
     
    
e1b1002129fe

When using the new sessionAttributeValueClassNameFilter, apply the filter earlier rather than loading the class and then deciding to filter it out.

https://github.com/apache/tomcatMark ThomasJan 21, 2016via ghsa
8 files changed · +146 15
  • java/org/apache/catalina/session/ManagerBase.java+15 1 modified
    @@ -37,6 +37,7 @@
     import org.apache.catalina.Container;
     import org.apache.catalina.Context;
     import org.apache.catalina.Engine;
    +import org.apache.catalina.Globals;
     import org.apache.catalina.Lifecycle;
     import org.apache.catalina.LifecycleException;
     import org.apache.catalina.LifecycleState;
    @@ -193,7 +194,20 @@ public abstract class ManagerBase extends LifecycleMBeanBase implements Manager
         private boolean warnOnSessionAttributeFilterFailure;
     
     
    -    // ------------------------------------------------------------- Properties
    +    // ------------------------------------------------------------ Constructors
    +
    +    public ManagerBase() {
    +        if (Globals.IS_SECURITY_ENABLED) {
    +            // Minimum set required for default distribution/persistence to work
    +            // plus String
    +            setSessionAttributeValueClassNameFilter(
    +                    "java\\.lang\\.(?:Boolean|Integer|Long|Number|String)");
    +            setWarnOnSessionAttributeFilterFailure(true);
    +        }
    +    }
    +
    +
    +    // -------------------------------------------------------------- Properties
     
         /**
          * Obtain the regular expression used to filter session attribute based on
    
  • java/org/apache/catalina/session/StandardManager.java+6 2 modified
    @@ -191,10 +191,12 @@ protected void doLoad() throws ClassNotFoundException, IOException {
             }
             Loader loader = null;
             ClassLoader classLoader = null;
    +        Log logger = null;
             try (FileInputStream fis = new FileInputStream(file.getAbsolutePath());
    -                BufferedInputStream bis = new BufferedInputStream(fis);) {
    +                BufferedInputStream bis = new BufferedInputStream(fis)) {
                 Context c = getContext();
                 loader = c.getLoader();
    +            logger = c.getLogger();
                 if (loader != null) {
                     classLoader = loader.getClassLoader();
                 }
    @@ -204,7 +206,9 @@ protected void doLoad() throws ClassNotFoundException, IOException {
     
                 // Load the previously unloaded active sessions
                 synchronized (sessions) {
    -                try (ObjectInputStream ois = new CustomObjectInputStream(bis, classLoader)) {
    +                try (ObjectInputStream ois = new CustomObjectInputStream(bis, classLoader, logger,
    +                        getSessionAttributeValueClassNamePattern(),
    +                        getWarnOnSessionAttributeFilterFailure())) {
                         Integer count = (Integer) ois.readObject();
                         int n = count.intValue();
                         if (log.isDebugEnabled())
    
  • java/org/apache/catalina/session/StoreBase.java+14 1 modified
    @@ -215,7 +215,20 @@ public void processExpires() {
          */
         protected ObjectInputStream getObjectInputStream(InputStream is) throws IOException {
             BufferedInputStream bis = new BufferedInputStream(is);
    -        return new CustomObjectInputStream(bis, Thread.currentThread().getContextClassLoader());
    +
    +        CustomObjectInputStream ois;
    +        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    +
    +        if (manager instanceof ManagerBase) {
    +            ManagerBase managerBase = (ManagerBase) manager;
    +            ois = new CustomObjectInputStream(bis, classLoader, manager.getContext().getLogger(),
    +                    managerBase.getSessionAttributeValueClassNamePattern(),
    +                    managerBase.getWarnOnSessionAttributeFilterFailure());
    +        } else {
    +            ois = new CustomObjectInputStream(bis, classLoader);
    +        }
    +
    +        return ois;
         }
     
     
    
  • java/org/apache/catalina/util/CustomObjectInputStream.java+87 2 modified
    @@ -18,9 +18,18 @@
     
     import java.io.IOException;
     import java.io.InputStream;
    +import java.io.InvalidClassException;
     import java.io.ObjectInputStream;
     import java.io.ObjectStreamClass;
     import java.lang.reflect.Proxy;
    +import java.util.Collections;
    +import java.util.Set;
    +import java.util.WeakHashMap;
    +import java.util.concurrent.ConcurrentHashMap;
    +import java.util.regex.Pattern;
    +
    +import org.apache.juli.logging.Log;
    +import org.apache.tomcat.util.res.StringManager;
     
     /**
      * Custom subclass of <code>ObjectInputStream</code> that loads from the
    @@ -32,23 +41,83 @@
      */
     public final class CustomObjectInputStream extends ObjectInputStream {
     
    +    private static final StringManager sm = StringManager.getManager(CustomObjectInputStream.class);
    +
    +    private static final WeakHashMap<ClassLoader, Set<String>> reportedClassCache =
    +            new WeakHashMap<>();
    +
         /**
          * The class loader we will use to resolve classes.
          */
         private final ClassLoader classLoader;
    +    private final Set<String> reportedClasses;
    +    private final Log log;
    +
    +    private final Pattern allowedClassNamePattern;
    +    private final String allowedClassNameFilter;
    +    private final boolean warnOnFailure;
     
     
         /**
    -     * Construct a new instance of CustomObjectInputStream
    +     * Construct a new instance of CustomObjectInputStream without any filtering
    +     * of deserialized classes.
          *
          * @param stream The input stream we will read from
          * @param classLoader The class loader used to instantiate objects
          *
          * @exception IOException if an input/output error occurs
          */
         public CustomObjectInputStream(InputStream stream, ClassLoader classLoader) throws IOException {
    +        this(stream, classLoader, null, null, false);
    +    }
    +
    +
    +    /**
    +     * Construct a new instance of CustomObjectInputStream with filtering of
    +     * deserialized classes.
    +     *
    +     * @param stream The input stream we will read from
    +     * @param classLoader The class loader used to instantiate objects
    +     * @param log The logger to use to report any issues. It may only be null if
    +     *            the filterMode does not require logging
    +     * @param allowedClassNamePattern The regular expression to use to filter
    +     *                                deserialized classes. The fully qualified
    +     *                                class name must match this pattern for
    +     *                                deserialization to be allowed if filtering
    +     *                                is enabled.
    +     * @param warnOnFailure Should any failures be logged?
    +     *
    +     * @exception IOException if an input/output error occurs
    +     */
    +    public CustomObjectInputStream(InputStream stream, ClassLoader classLoader,
    +            Log log, Pattern allowedClassNamePattern, boolean warnOnFailure)
    +            throws IOException {
             super(stream);
    +        if (log == null && allowedClassNamePattern != null && warnOnFailure) {
    +            throw new IllegalArgumentException(
    +                    sm.getString("customObjectInputStream.logRequired"));
    +        }
             this.classLoader = classLoader;
    +        this.log = log;
    +        this.allowedClassNamePattern = allowedClassNamePattern;
    +        if (allowedClassNamePattern == null) {
    +            this.allowedClassNameFilter = null;
    +        } else {
    +            this.allowedClassNameFilter = allowedClassNamePattern.toString();
    +        }
    +        this.warnOnFailure = warnOnFailure;
    +
    +        Set<String> reportedClasses = reportedClassCache.get(classLoader);
    +        if (reportedClasses == null) {
    +            reportedClasses = Collections.newSetFromMap(new ConcurrentHashMap<>());
    +            Set<String> original = reportedClassCache.putIfAbsent(classLoader, reportedClasses);
    +            if (original != null) {
    +                // Concurrent attempts to create the new Set. Make sure all
    +                // threads use the first successfully added Set.
    +                reportedClasses = original;
    +            }
    +        }
    +        this.reportedClasses = reportedClasses;
         }
     
     
    @@ -64,8 +133,24 @@ public CustomObjectInputStream(InputStream stream, ClassLoader classLoader) thro
         @Override
         public Class<?> resolveClass(ObjectStreamClass classDesc)
             throws ClassNotFoundException, IOException {
    +
    +        String name = classDesc.getName();
    +        if (allowedClassNamePattern != null) {
    +            boolean allowed = allowedClassNamePattern.matcher(name).matches();
    +            if (!allowed) {
    +                boolean doLog = warnOnFailure && reportedClasses.add(name);
    +                String msg = sm.getString("customObjectInputStream.nomatch", name, allowedClassNameFilter);
    +                if (doLog) {
    +                    log.warn(msg);
    +                } else if (log.isDebugEnabled()) {
    +                    log.debug(msg);
    +                }
    +                throw new InvalidClassException(msg);
    +            }
    +        }
    +
             try {
    -            return Class.forName(classDesc.getName(), false, classLoader);
    +            return Class.forName(name, false, classLoader);
             } catch (ClassNotFoundException e) {
                 try {
                     // Try also the superclass because of primitive types
    
  • java/org/apache/catalina/util/LocalStrings.properties+2 0 modified
    @@ -17,6 +17,8 @@ parameterMap.locked=No modifications are allowed to a locked ParameterMap
     resourceSet.locked=No modifications are allowed to a locked ResourceSet
     hexUtil.bad=Bad hexadecimal digit
     hexUtil.odd=Odd number of hexadecimal digits
    +customObjectInputStream.logRequired=A valid logger is required for class name filtering with logging
    +customObjectInputStream.nomatch=The class [{0}] did not match the regular expression [{1}] for classes allowed to be deserialized
     #Default Messages Utilized by the ExtensionValidator
     extensionValidator.web-application-manifest=Web Application Manifest
     extensionValidator.extension-not-found-error=ExtensionValidator[{0}][{1}]: Required extension [{2}] not found.
    
  • webapps/docs/changelog.xml+2 1 modified
    @@ -212,7 +212,8 @@
             based on the implementation class of the value and optional
             <code>WARN</code> level logging if an attribute is filtered. These
             options are avaialble for all of the Manager implementations that ship
    -        with Tomcat. (markt)
    +        with Tomcat. When a <code>SecurityManager</code> is used filtering will
    +        be enabled by default. (markt)
           </add>
           <scode>
             Remove <code>distributable</code> and <code>maxInactiveInterval</code>
    
  • webapps/docs/config/cluster-manager.xml+10 4 modified
    @@ -180,7 +180,9 @@
             length or <code>null</code>, all attributes are eligible for
             replication. The pattern is anchored so the fully qualified class name
             must fully match the pattern. If not specified, the default value of
    -        <code>null</code> will be used.</p>
    +        <code>null</code> will be used unless a <code>SecurityManager</code> is
    +        enabled in which case the default will be
    +        <code>java\\.lang\\.(?:Boolean|Integer|Long|Number|String)</code>.</p>
           </attribute>
           <attribute name="stateTimestampDrop" required="false">
             When this node sends a <code>GET_ALL_SESSIONS</code> message to other
    @@ -199,7 +201,8 @@
             attribute, should this be logged at <code>WARN</code> level? If
             <code>WARN</code> level logging is disabled then it will be logged at
             <code>DEBUG</code>. The default value of this attribute is
    -        <code>false</code>.</p>
    +        <code>false</code> unless a <code>SecurityManager</code> is enabled in
    +        which case the default will be <code>true</code>.</p>
           </attribute>
         </attributes>
       </subsection>
    @@ -242,7 +245,9 @@
             length or <code>null</code>, all attributes are eligible for
             replication. The pattern is anchored so the fully qualified class name
             must fully match the pattern. If not specified, the default value of
    -        <code>null</code> will be used.</p>
    +        <code>null</code> will be used unless a <code>SecurityManager</code> is
    +        enabled in which case the default will be
    +        <code>java\\.lang\\.(?:Boolean|Integer|Long|Number|String)</code>.</p>
           </attribute>
           <attribute name="terminateOnStartFailure" required="false">
             Set to true if you wish to terminate replication map when replication
    @@ -257,7 +262,8 @@
             attribute, should this be logged at <code>WARN</code> level? If
             <code>WARN</code> level logging is disabled then it will be logged at
             <code>DEBUG</code>. The default value of this attribute is
    -        <code>false</code>.</p>
    +        <code>false</code> unless a <code>SecurityManager</code> is enabled in
    +        which case the default will be <code>true</code>.</p>
           </attribute>
         </attributes>
       </subsection>
    
  • webapps/docs/config/manager.xml+10 4 modified
    @@ -159,7 +159,9 @@
             length or <code>null</code>, all attributes are eligible for
             distribution. The pattern is anchored so the fully qualified class name
             must fully match the pattern. If not specified, the default value of
    -        <code>null</code> will be used.</p>
    +        <code>null</code> will be used unless a <code>SecurityManager</code> is
    +        enabled in which case the default will be
    +        <code>java\\.lang\\.(?:Boolean|Integer|Long|Number|String)</code>.</p>
           </attribute>
     
           <attribute name="warnOnSessionAttributeFilterFailure" required="false">
    @@ -168,7 +170,8 @@
             attribute, should this be logged at <code>WARN</code> level? If
             <code>WARN</code> level logging is disabled then it will be logged at
             <code>DEBUG</code>. The default value of this attribute is
    -        <code>false</code>.</p>
    +        <code>false</code> unless a <code>SecurityManager</code> is enabled in
    +        which case the default will be <code>true</code>.</p>
           </attribute>
         </attributes>
     
    @@ -279,7 +282,9 @@
             length or <code>null</code>, all attributes are eligible for
             distribution. The pattern is anchored so the fully qualified class name
             must fully match the pattern. If not specified, the default value of
    -        <code>null</code> will be used.</p>
    +        <code>null</code> will be used unless a <code>SecurityManager</code> is
    +        enabled in which case the default will be
    +        <code>java\\.lang\\.(?:Boolean|Integer|Long|Number|String)</code>.</p>
           </attribute>
     
           <attribute name="warnOnSessionAttributeFilterFailure" required="false">
    @@ -288,7 +293,8 @@
             attribute, should this be logged at <code>WARN</code> level? If
             <code>WARN</code> level logging is disabled then it will be logged at
             <code>DEBUG</code>. The default value of this attribute is
    -        <code>false</code>.</p>
    +        <code>false</code> unless a <code>SecurityManager</code> is enabled in
    +        which case the default will be <code>true</code>.</p>
           </attribute>
         </attributes>
     
    
f626da75fd59

When using the new sessionAttributeValueClassNameFilter, apply the filter earlier rather than loading the class and then deciding to filter it out.

https://github.com/apache/tomcatMark ThomasJan 21, 2016via ghsa
8 files changed · +146 15
  • java/org/apache/catalina/session/ManagerBase.java+15 1 modified
    @@ -37,6 +37,7 @@
     import org.apache.catalina.Container;
     import org.apache.catalina.Context;
     import org.apache.catalina.Engine;
    +import org.apache.catalina.Globals;
     import org.apache.catalina.Lifecycle;
     import org.apache.catalina.LifecycleException;
     import org.apache.catalina.LifecycleState;
    @@ -193,7 +194,20 @@ public abstract class ManagerBase extends LifecycleMBeanBase implements Manager
         private boolean warnOnSessionAttributeFilterFailure;
     
     
    -    // ------------------------------------------------------------- Properties
    +    // ------------------------------------------------------------ Constructors
    +
    +    public ManagerBase() {
    +        if (Globals.IS_SECURITY_ENABLED) {
    +            // Minimum set required for default distribution/persistence to work
    +            // plus String
    +            setSessionAttributeValueClassNameFilter(
    +                    "java\\.lang\\.(?:Boolean|Integer|Long|Number|String)");
    +            setWarnOnSessionAttributeFilterFailure(true);
    +        }
    +    }
    +
    +
    +    // -------------------------------------------------------------- Properties
     
         /**
          * Obtain the regular expression used to filter session attribute based on
    
  • java/org/apache/catalina/session/StandardManager.java+6 2 modified
    @@ -191,10 +191,12 @@ protected void doLoad() throws ClassNotFoundException, IOException {
             }
             Loader loader = null;
             ClassLoader classLoader = null;
    +        Log logger = null;
             try (FileInputStream fis = new FileInputStream(file.getAbsolutePath());
    -                BufferedInputStream bis = new BufferedInputStream(fis);) {
    +                BufferedInputStream bis = new BufferedInputStream(fis)) {
                 Context c = getContext();
                 loader = c.getLoader();
    +            logger = c.getLogger();
                 if (loader != null) {
                     classLoader = loader.getClassLoader();
                 }
    @@ -204,7 +206,9 @@ protected void doLoad() throws ClassNotFoundException, IOException {
     
                 // Load the previously unloaded active sessions
                 synchronized (sessions) {
    -                try (ObjectInputStream ois = new CustomObjectInputStream(bis, classLoader)) {
    +                try (ObjectInputStream ois = new CustomObjectInputStream(bis, classLoader, logger,
    +                        getSessionAttributeValueClassNamePattern(),
    +                        getWarnOnSessionAttributeFilterFailure())) {
                         Integer count = (Integer) ois.readObject();
                         int n = count.intValue();
                         if (log.isDebugEnabled())
    
  • java/org/apache/catalina/session/StoreBase.java+14 1 modified
    @@ -215,7 +215,20 @@ public void processExpires() {
          */
         protected ObjectInputStream getObjectInputStream(InputStream is) throws IOException {
             BufferedInputStream bis = new BufferedInputStream(is);
    -        return new CustomObjectInputStream(bis, Thread.currentThread().getContextClassLoader());
    +
    +        CustomObjectInputStream ois;
    +        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    +
    +        if (manager instanceof ManagerBase) {
    +            ManagerBase managerBase = (ManagerBase) manager;
    +            ois = new CustomObjectInputStream(bis, classLoader, manager.getContext().getLogger(),
    +                    managerBase.getSessionAttributeValueClassNamePattern(),
    +                    managerBase.getWarnOnSessionAttributeFilterFailure());
    +        } else {
    +            ois = new CustomObjectInputStream(bis, classLoader);
    +        }
    +
    +        return ois;
         }
     
     
    
  • java/org/apache/catalina/util/CustomObjectInputStream.java+87 2 modified
    @@ -18,9 +18,18 @@
     
     import java.io.IOException;
     import java.io.InputStream;
    +import java.io.InvalidClassException;
     import java.io.ObjectInputStream;
     import java.io.ObjectStreamClass;
     import java.lang.reflect.Proxy;
    +import java.util.Collections;
    +import java.util.Set;
    +import java.util.WeakHashMap;
    +import java.util.concurrent.ConcurrentHashMap;
    +import java.util.regex.Pattern;
    +
    +import org.apache.juli.logging.Log;
    +import org.apache.tomcat.util.res.StringManager;
     
     /**
      * Custom subclass of <code>ObjectInputStream</code> that loads from the
    @@ -32,23 +41,83 @@
      */
     public final class CustomObjectInputStream extends ObjectInputStream {
     
    +    private static final StringManager sm = StringManager.getManager(CustomObjectInputStream.class);
    +
    +    private static final WeakHashMap<ClassLoader, Set<String>> reportedClassCache =
    +            new WeakHashMap<>();
    +
         /**
          * The class loader we will use to resolve classes.
          */
         private final ClassLoader classLoader;
    +    private final Set<String> reportedClasses;
    +    private final Log log;
    +
    +    private final Pattern allowedClassNamePattern;
    +    private final String allowedClassNameFilter;
    +    private final boolean warnOnFailure;
     
     
         /**
    -     * Construct a new instance of CustomObjectInputStream
    +     * Construct a new instance of CustomObjectInputStream without any filtering
    +     * of deserialized classes.
          *
          * @param stream The input stream we will read from
          * @param classLoader The class loader used to instantiate objects
          *
          * @exception IOException if an input/output error occurs
          */
         public CustomObjectInputStream(InputStream stream, ClassLoader classLoader) throws IOException {
    +        this(stream, classLoader, null, null, false);
    +    }
    +
    +
    +    /**
    +     * Construct a new instance of CustomObjectInputStream with filtering of
    +     * deserialized classes.
    +     *
    +     * @param stream The input stream we will read from
    +     * @param classLoader The class loader used to instantiate objects
    +     * @param log The logger to use to report any issues. It may only be null if
    +     *            the filterMode does not require logging
    +     * @param allowedClassNamePattern The regular expression to use to filter
    +     *                                deserialized classes. The fully qualified
    +     *                                class name must match this pattern for
    +     *                                deserialization to be allowed if filtering
    +     *                                is enabled.
    +     * @param warnOnFailure Should any failures be logged?
    +     *
    +     * @exception IOException if an input/output error occurs
    +     */
    +    public CustomObjectInputStream(InputStream stream, ClassLoader classLoader,
    +            Log log, Pattern allowedClassNamePattern, boolean warnOnFailure)
    +            throws IOException {
             super(stream);
    +        if (log == null && allowedClassNamePattern != null && warnOnFailure) {
    +            throw new IllegalArgumentException(
    +                    sm.getString("customObjectInputStream.logRequired"));
    +        }
             this.classLoader = classLoader;
    +        this.log = log;
    +        this.allowedClassNamePattern = allowedClassNamePattern;
    +        if (allowedClassNamePattern == null) {
    +            this.allowedClassNameFilter = null;
    +        } else {
    +            this.allowedClassNameFilter = allowedClassNamePattern.toString();
    +        }
    +        this.warnOnFailure = warnOnFailure;
    +
    +        Set<String> reportedClasses = reportedClassCache.get(classLoader);
    +        if (reportedClasses == null) {
    +            reportedClasses = Collections.newSetFromMap(new ConcurrentHashMap<>());
    +            Set<String> original = reportedClassCache.putIfAbsent(classLoader, reportedClasses);
    +            if (original != null) {
    +                // Concurrent attempts to create the new Set. Make sure all
    +                // threads use the first successfully added Set.
    +                reportedClasses = original;
    +            }
    +        }
    +        this.reportedClasses = reportedClasses;
         }
     
     
    @@ -64,8 +133,24 @@ public CustomObjectInputStream(InputStream stream, ClassLoader classLoader) thro
         @Override
         public Class<?> resolveClass(ObjectStreamClass classDesc)
             throws ClassNotFoundException, IOException {
    +
    +        String name = classDesc.getName();
    +        if (allowedClassNamePattern != null) {
    +            boolean allowed = allowedClassNamePattern.matcher(name).matches();
    +            if (!allowed) {
    +                boolean doLog = warnOnFailure && reportedClasses.add(name);
    +                String msg = sm.getString("customObjectInputStream.nomatch", name, allowedClassNameFilter);
    +                if (doLog) {
    +                    log.warn(msg);
    +                } else if (log.isDebugEnabled()) {
    +                    log.debug(msg);
    +                }
    +                throw new InvalidClassException(msg);
    +            }
    +        }
    +
             try {
    -            return Class.forName(classDesc.getName(), false, classLoader);
    +            return Class.forName(name, false, classLoader);
             } catch (ClassNotFoundException e) {
                 try {
                     // Try also the superclass because of primitive types
    
  • java/org/apache/catalina/util/LocalStrings.properties+2 0 modified
    @@ -17,6 +17,8 @@ parameterMap.locked=No modifications are allowed to a locked ParameterMap
     resourceSet.locked=No modifications are allowed to a locked ResourceSet
     hexUtil.bad=Bad hexadecimal digit
     hexUtil.odd=Odd number of hexadecimal digits
    +customObjectInputStream.logRequired=A valid logger is required for class name filtering with logging
    +customObjectInputStream.nomatch=The class [{0}] did not match the regular expression [{1}] for classes allowed to be deserialized
     #Default Messages Utilized by the ExtensionValidator
     extensionValidator.web-application-manifest=Web Application Manifest
     extensionValidator.extension-not-found-error=ExtensionValidator[{0}][{1}]: Required extension [{2}] not found.
    
  • webapps/docs/changelog.xml+2 1 modified
    @@ -212,7 +212,8 @@
             based on the implementation class of the value and optional
             <code>WARN</code> level logging if an attribute is filtered. These
             options are avaialble for all of the Manager implementations that ship
    -        with Tomcat. (markt)
    +        with Tomcat. When a <code>SecurityManager</code> is used filtering will
    +        be enabled by default. (markt)
           </add>
           <scode>
             Remove <code>distributable</code> and <code>maxInactiveInterval</code>
    
  • webapps/docs/config/cluster-manager.xml+10 4 modified
    @@ -180,7 +180,9 @@
             length or <code>null</code>, all attributes are eligible for
             replication. The pattern is anchored so the fully qualified class name
             must fully match the pattern. If not specified, the default value of
    -        <code>null</code> will be used.</p>
    +        <code>null</code> will be used unless a <code>SecurityManager</code> is
    +        enabled in which case the default will be
    +        <code>java\\.lang\\.(?:Boolean|Integer|Long|Number|String)</code>.</p>
           </attribute>
           <attribute name="stateTimestampDrop" required="false">
             When this node sends a <code>GET_ALL_SESSIONS</code> message to other
    @@ -199,7 +201,8 @@
             attribute, should this be logged at <code>WARN</code> level? If
             <code>WARN</code> level logging is disabled then it will be logged at
             <code>DEBUG</code>. The default value of this attribute is
    -        <code>false</code>.</p>
    +        <code>false</code> unless a <code>SecurityManager</code> is enabled in
    +        which case the default will be <code>true</code>.</p>
           </attribute>
         </attributes>
       </subsection>
    @@ -242,7 +245,9 @@
             length or <code>null</code>, all attributes are eligible for
             replication. The pattern is anchored so the fully qualified class name
             must fully match the pattern. If not specified, the default value of
    -        <code>null</code> will be used.</p>
    +        <code>null</code> will be used unless a <code>SecurityManager</code> is
    +        enabled in which case the default will be
    +        <code>java\\.lang\\.(?:Boolean|Integer|Long|Number|String)</code>.</p>
           </attribute>
           <attribute name="terminateOnStartFailure" required="false">
             Set to true if you wish to terminate replication map when replication
    @@ -257,7 +262,8 @@
             attribute, should this be logged at <code>WARN</code> level? If
             <code>WARN</code> level logging is disabled then it will be logged at
             <code>DEBUG</code>. The default value of this attribute is
    -        <code>false</code>.</p>
    +        <code>false</code> unless a <code>SecurityManager</code> is enabled in
    +        which case the default will be <code>true</code>.</p>
           </attribute>
         </attributes>
       </subsection>
    
  • webapps/docs/config/manager.xml+10 4 modified
    @@ -159,7 +159,9 @@
             length or <code>null</code>, all attributes are eligible for
             distribution. The pattern is anchored so the fully qualified class name
             must fully match the pattern. If not specified, the default value of
    -        <code>null</code> will be used.</p>
    +        <code>null</code> will be used unless a <code>SecurityManager</code> is
    +        enabled in which case the default will be
    +        <code>java\\.lang\\.(?:Boolean|Integer|Long|Number|String)</code>.</p>
           </attribute>
     
           <attribute name="warnOnSessionAttributeFilterFailure" required="false">
    @@ -168,7 +170,8 @@
             attribute, should this be logged at <code>WARN</code> level? If
             <code>WARN</code> level logging is disabled then it will be logged at
             <code>DEBUG</code>. The default value of this attribute is
    -        <code>false</code>.</p>
    +        <code>false</code> unless a <code>SecurityManager</code> is enabled in
    +        which case the default will be <code>true</code>.</p>
           </attribute>
         </attributes>
     
    @@ -279,7 +282,9 @@
             length or <code>null</code>, all attributes are eligible for
             distribution. The pattern is anchored so the fully qualified class name
             must fully match the pattern. If not specified, the default value of
    -        <code>null</code> will be used.</p>
    +        <code>null</code> will be used unless a <code>SecurityManager</code> is
    +        enabled in which case the default will be
    +        <code>java\\.lang\\.(?:Boolean|Integer|Long|Number|String)</code>.</p>
           </attribute>
     
           <attribute name="warnOnSessionAttributeFilterFailure" required="false">
    @@ -288,7 +293,8 @@
             attribute, should this be logged at <code>WARN</code> level? If
             <code>WARN</code> level logging is disabled then it will be logged at
             <code>DEBUG</code>. The default value of this attribute is
    -        <code>false</code>.</p>
    +        <code>false</code> unless a <code>SecurityManager</code> is enabled in
    +        which case the default will be <code>true</code>.</p>
           </attribute>
         </attributes>
     
    
824eb1d1ad92

Expand the session attribute filtering options

https://github.com/apache/tomcatMark ThomasJan 18, 2016via ghsa
7 files changed · +256 18
  • java/org/apache/catalina/ha/session/mbeans-descriptors.xml+16 0 modified
    @@ -314,6 +314,14 @@
           name="sessionAttributeNameFilter"
           descritpion="The string pattern used for including session attributes in replication. Null means all attributes are included."
           type="java.lang.String"/>
    +    <attribute
    +      name="sessionAttributeValueClassNameFilter"
    +      description="The regular expression used to filter session attributes based on the implementation class of the value. The regular expression is anchored and must match the fully qualified class name."
    +      type="java.lang.String"/>
    +    <attribute
    +      name="warnOnSessionAttributeFilterFailure"
    +      description="Should a WARN level log message be generated if a session attribute fails to match sessionAttributeNameFilter or sessionAttributeClassNameFilter?"
    +      type="boolean"/>
         <operation
           name="expireSession"
           description="Expired the given session"
    @@ -536,6 +544,14 @@
           name="sessionAttributeNameFilter"
           descritpion="The string pattern used for including session attributes in replication. Null means all attributes are included."
           type="java.lang.String"/>
    +    <attribute
    +      name="sessionAttributeValueClassNameFilter"
    +      description="The regular expression used to filter session attributes based on the implementation class of the value. The regular expression is anchored and must match the fully qualified class name."
    +      type="java.lang.String"/>
    +    <attribute
    +      name="warnOnSessionAttributeFilterFailure"
    +      description="Should a WARN level log message be generated if a session attribute fails to match sessionAttributeNameFilter or sessionAttributeClassNameFilter?"
    +      type="boolean"/>
         <operation
           name="expireSession"
           description="Expired the given session"
    
  • java/org/apache/catalina/session/LocalStrings.properties+2 0 modified
    @@ -32,6 +32,8 @@ JDBCStore.missingDataSourceName=No valid JNDI name was given.
     JDBCStore.commitSQLException=SQLException committing connection before closing
     managerBase.container.noop=Managers added to containers other than Contexts will never be used
     managerBase.createSession.ise=createSession: Too many active sessions
    +managerBase.sessionAttributeNameFilter=Skipped session attribute named [{0}] because it did not match the name filter [{1}]
    +managerBase.sessionAttributeValueClassNameFilter=Skipped session attribute named [{0}] because the value type [{1}] did not match the filter [{2}]
     managerBase.sessionTimeout=Invalid session timeout setting {0}
     standardManager.loading=Loading persisted sessions from {0}
     standardManager.loading.exception=Exception while loading persisted sessions
    
  • java/org/apache/catalina/session/ManagerBase.java+116 3 modified
    @@ -203,6 +203,10 @@ public abstract class ManagerBase extends LifecycleMBeanBase
     
         private Pattern sessionAttributeNamePattern;
     
    +    private Pattern sessionAttributeValueClassNamePattern;
    +
    +    private boolean warnOnSessionAttributeFilterFailure;
    +
     
         // ------------------------------------------------------------- Properties
     
    @@ -257,6 +261,86 @@ protected Pattern getSessionAttributeNamePattern() {
         }
     
     
    +    /**
    +     * Obtain the regular expression used to filter session attribute based on
    +     * the implementation class of the value. The regular expression is anchored
    +     * and must match the fully qualified class name.
    +     *
    +     * @return The regular expression currently used to filter class names.
    +     *         {@code null} means no filter is applied. If an empty string is
    +     *         specified then no names will match the filter and all attributes
    +     *         will be blocked.
    +     */
    +    public String getSessionAttributeValueClassNameFilter() {
    +        if (sessionAttributeValueClassNamePattern == null) {
    +            return null;
    +        }
    +        return sessionAttributeValueClassNamePattern.toString();
    +    }
    +
    +
    +    /**
    +     * Provides {@link #getSessionAttributeValueClassNameFilter()} as a
    +     * pre-compiled regular expression pattern.
    +     *
    +     * @return The pre-compiled pattern used to filter session attributes based
    +     *         on the implementation class name of the value. {@code null} means
    +     *         no filter is applied.
    +     */
    +    protected Pattern getSessionAttributeValueClassNamePattern() {
    +        return sessionAttributeValueClassNamePattern;
    +    }
    +
    +
    +    /**
    +     * Set the regular expression to use to filter classes used for session
    +     * attributes. The regular expression is anchored and must match the fully
    +     * qualified class name.
    +     *
    +     * @param sessionAttributeValueClassNameFilter The regular expression to use
    +     *            to filter session attributes based on class name. Use {@code
    +     *            null} if no filtering is required. If an empty string is
    +     *           specified then no names will match the filter and all
    +     *           attributes will be blocked.
    +     *
    +     * @throws PatternSyntaxException If the expression is not valid
    +     */
    +    public void setSessionAttributeValueClassNameFilter(String sessionAttributeValueClassNameFilter)
    +            throws PatternSyntaxException {
    +        if (sessionAttributeValueClassNameFilter == null ||
    +                sessionAttributeValueClassNameFilter.length() == 0) {
    +            sessionAttributeValueClassNamePattern = null;
    +        }
    +        sessionAttributeValueClassNamePattern =
    +                Pattern.compile(sessionAttributeValueClassNameFilter);
    +    }
    +
    +
    +    /**
    +     * Should a warn level log message be generated if a session attribute is
    +     * not persisted / replicated / restored.
    +     *
    +     * @return {@code true} if a warn level log message should be generated
    +     */
    +    public boolean getWarnOnSessionAttributeFilterFailure() {
    +        return warnOnSessionAttributeFilterFailure;
    +    }
    +
    +
    +    /**
    +     * Configure whether or not a warn level log message should be generated if
    +     * a session attribute is not persisted / replicated / restored.
    +     *
    +     * @param warnOnSessionAttributeFilterFailure {@code true} if the
    +     *            warn level message should be generated
    +     *
    +     */
    +    public void setWarnOnSessionAttributeFilterFailure(
    +            boolean warnOnSessionAttributeFilterFailure) {
    +        this.warnOnSessionAttributeFilterFailure = warnOnSessionAttributeFilterFailure;
    +    }
    +
    +
         @Override
         public Context getContext() {
             return context;
    @@ -719,10 +803,39 @@ protected void changeSessionId(Session session, String newId,
         @Override
         public boolean willAttributeDistribute(String name, Object value) {
             Pattern sessionAttributeNamePattern = getSessionAttributeNamePattern();
    -        if (sessionAttributeNamePattern == null) {
    -            return true;
    +        if (sessionAttributeNamePattern != null) {
    +            if (!sessionAttributeNamePattern.matcher(name).matches()) {
    +                if (getWarnOnSessionAttributeFilterFailure() || log.isDebugEnabled()) {
    +                    String msg = sm.getString("managerBase.sessionAttributeNameFilter",
    +                            name, sessionAttributeNamePattern);
    +                    if (getWarnOnSessionAttributeFilterFailure()) {
    +                        log.warn(msg);
    +                    } else {
    +                        log.debug(msg);
    +                    }
    +                }
    +                return false;
    +            }
    +        }
    +
    +        Pattern sessionAttributeValueClassNamePattern = getSessionAttributeValueClassNamePattern();
    +        if (value != null && sessionAttributeValueClassNamePattern != null) {
    +            if (!sessionAttributeValueClassNamePattern.matcher(
    +                    value.getClass().getName()).matches()) {
    +                if (getWarnOnSessionAttributeFilterFailure() || log.isDebugEnabled()) {
    +                    String msg = sm.getString("managerBase.sessionAttributeValueClassNameFilter",
    +                            name, value.getClass().getName(), sessionAttributeNamePattern);
    +                    if (getWarnOnSessionAttributeFilterFailure()) {
    +                        log.warn(msg);
    +                    } else {
    +                        log.debug(msg);
    +                    }
    +                }
    +                return false;
    +            }
             }
    -        return sessionAttributeNamePattern.matcher(name).matches();
    +
    +        return true;
         }
     
     
    
  • java/org/apache/catalina/session/mbeans-descriptors.xml+16 0 modified
    @@ -132,6 +132,14 @@
               descritpion="The string pattern used for including session attributes in distribution. Null means all attributes are included."
                      type="java.lang.String"/>
     
    +    <attribute   name="sessionAttributeValueClassNameFilter"
    +          description="The regular expression used to filter session attributes based on the implementation class of the value. The regular expression is anchored and must match the fully qualified class name."
    +                 type="java.lang.String"/>
    +
    +    <attribute   name="warnOnSessionAttributeFilterFailure"
    +          description="Should a WARN level log message be generated if a session attribute fails to match sessionAttributeNameFilter or sessionAttributeClassNameFilter?"
    +                 type="boolean"/>
    +
         <operation   name="backgroundProcess"
               description="Invalidate all sessions that have expired."
                    impact="ACTION"
    @@ -319,6 +327,14 @@
               descritpion="The string pattern used for including session attributes in distribution. Null means all attributes are included."
                      type="java.lang.String"/>
     
    +    <attribute   name="sessionAttributeValueClassNameFilter"
    +          description="The regular expression used to filter session attributes based on the implementation class of the value. The regular expression is anchored and must match the fully qualified class name."
    +                 type="java.lang.String"/>
    +
    +    <attribute   name="warnOnSessionAttributeFilterFailure"
    +          description="Should a WARN level log message be generated if a session attribute fails to match sessionAttributeNameFilter or sessionAttributeClassNameFilter?"
    +                 type="boolean"/>
    +
         <operation   name="backgroundProcess"
               description="Invalidate all sessions that have expired."
                    impact="ACTION"
    
  • webapps/docs/changelog.xml+7 0 modified
    @@ -207,6 +207,13 @@
             well as unload to ensure that configuration changes made while the web
             application is stopped are applied to any persisted data. (markt)
           </add>
    +      <add>
    +        Extend the session attribute filtering options to include filtering
    +        based on the implementation class of the value and optional
    +        <code>WARN</code> level logging if an attribute is filtered. These
    +        options are avaialble for all of the Manager implementations that ship
    +        with Tomcat. (markt)
    +      </add>
         </changelog>
       </subsection>
       <subsection name="Coyote">
    
  • webapps/docs/config/cluster-manager.xml+56 11 modified
    @@ -77,17 +77,6 @@
             when session attributes are being replicated or removed across Tomcat
             nodes in the cluster.
           </attribute>
    -      <attribute name="sessionAttributeNameFilter" required="false">
    -        A regular expression used to filter which session attributes will be
    -        replicated. An attribute will only be replicated if its name matches
    -        this pattern. If the pattern is zero length or <code>null</code>, all
    -        attributes are eligible for replication. The pattern is anchored so the
    -        session attribute name must fully match the pattern. As an example, the
    -        value <code>(userName|sessionHistory)</code> will only replicate the
    -        two session attributes named <code>userName</code> and
    -        <code>sessionHistory</code>. If not specified, the default value of
    -        <code>null</code> will be used.
    -      </attribute>
           <attribute name="maxInactiveInterval" required="false">
             <p>The initial maximum time interval, in seconds,
             between client requests before a session is invalidated. A negative value
    @@ -184,6 +173,26 @@
             effective only when <code>sendAllSessions</code> is <code>false</code>.
             Default is <code>2000</code> milliseconds.
           </attribute>
    +      <attribute name="sessionAttributeNameFilter" required="false">
    +        <p>A regular expression used to filter which session attributes will be
    +        replicated. An attribute will only be replicated if its name matches
    +        this pattern. If the pattern is zero length or <code>null</code>, all
    +        attributes are eligible for replication. The pattern is anchored so the
    +        session attribute name must fully match the pattern. As an example, the
    +        value <code>(userName|sessionHistory)</code> will only replicate the
    +        two session attributes named <code>userName</code> and
    +        <code>sessionHistory</code>. If not specified, the default value of
    +        <code>null</code> will be used.</p>
    +      </attribute>
    +      <attribute name="sessionAttributeValueClassNameFilter" required="false">
    +        <p>A regular expression used to filter which session attributes will be
    +        replicated. An attribute will only be replicated if the implementation
    +        class name of the value matches this pattern. If the pattern is zero
    +        length or <code>null</code>, all attributes are eligible for
    +        replication. The pattern is anchored so the fully qualified class name
    +        must fully match the pattern. If not specified, the default value of
    +        <code>null</code> will be used.</p>
    +      </attribute>
           <attribute name="stateTimestampDrop" required="false">
             When this node sends a <code>GET_ALL_SESSIONS</code> message to other
             node, all session messages that are received as a response are queued.
    @@ -195,6 +204,14 @@
             If set to <code>false</code>, all queued session messages are handled.
             Default is <code>true</code>.
           </attribute>
    +      <attribute name="warnOnSessionAttributeFilterFailure" required="false">
    +        <p>If <strong>sessionAttributeNameFilter</strong> or
    +        <strong>sessionAttributeValueClassNameFilter</strong> blocks an
    +        attribute, should this be logged at <code>WARN</code> level? If
    +        <code>WARN</code> level logging is disabled then it will be logged at
    +        <code>DEBUG</code>. The default value of this attribute is
    +        <code>false</code>.</p>
    +      </attribute>
         </attributes>
       </subsection>
       <subsection name="org.apache.catalina.ha.session.BackupManager Attributes">
    @@ -218,13 +235,41 @@
             another map.
             Default value is <code>15000</code> milliseconds.
           </attribute>
    +      <attribute name="sessionAttributeNameFilter" required="false">
    +        <p>A regular expression used to filter which session attributes will be
    +        replicated. An attribute will only be replicated if its name matches
    +        this pattern. If the pattern is zero length or <code>null</code>, all
    +        attributes are eligible for replication. The pattern is anchored so the
    +        session attribute name must fully match the pattern. As an example, the
    +        value <code>(userName|sessionHistory)</code> will only replicate the
    +        two session attributes named <code>userName</code> and
    +        <code>sessionHistory</code>. If not specified, the default value of
    +        <code>null</code> will be used.</p>
    +      </attribute>
    +      <attribute name="sessionAttributeValueClassNameFilter" required="false">
    +        <p>A regular expression used to filter which session attributes will be
    +        replicated. An attribute will only be replicated if the implementation
    +        class name of the value matches this pattern. If the pattern is zero
    +        length or <code>null</code>, all attributes are eligible for
    +        replication. The pattern is anchored so the fully qualified class name
    +        must fully match the pattern. If not specified, the default value of
    +        <code>null</code> will be used.</p>
    +      </attribute>
           <attribute name="terminateOnStartFailure" required="false">
             Set to true if you wish to terminate replication map when replication
             map fails to start. If replication map is terminated, associated context
             will fail to start. If you set this attribute to false, replication map
             does not end. It will try to join the map membership in the heartbeat.
             Default value is <code>false</code> .
           </attribute>
    +      <attribute name="warnOnSessionAttributeFilterFailure" required="false">
    +        <p>If <strong>sessionAttributeNameFilter</strong> or
    +        <strong>sessionAttributeValueClassNameFilter</strong> blocks an
    +        attribute, should this be logged at <code>WARN</code> level? If
    +        <code>WARN</code> level logging is disabled then it will be logged at
    +        <code>DEBUG</code>. The default value of this attribute is
    +        <code>false</code>.</p>
    +      </attribute>
         </attributes>
       </subsection>
     </section>
    
  • webapps/docs/config/manager.xml+43 4 modified
    @@ -167,15 +167,34 @@
           </attribute>
     
           <attribute name="sessionAttributeNameFilter" required="false">
    -        A regular expression used to filter which session attributes will be
    +        <p>A regular expression used to filter which session attributes will be
             distributed. An attribute will only be distributed if its name matches
             this pattern. If the pattern is zero length or <code>null</code>, all
             attributes are eligible for distribution. The pattern is anchored so the
             session attribute name must fully match the pattern. As an example, the
             value <code>(userName|sessionHistory)</code> will only distribute the
             two session attributes named <code>userName</code> and
             <code>sessionHistory</code>. If not specified, the default value of
    -        <code>null</code> will be used.
    +        <code>null</code> will be used.</p>
    +      </attribute>
    +
    +      <attribute name="sessionAttributeValueClassNameFilter" required="false">
    +        <p>A regular expression used to filter which session attributes will be
    +        distributed. An attribute will only be distributed if the implementation
    +        class name of the value matches this pattern. If the pattern is zero
    +        length or <code>null</code>, all attributes are eligible for
    +        distribution. The pattern is anchored so the fully qualified class name
    +        must fully match the pattern. If not specified, the default value of
    +        <code>null</code> will be used.</p>
    +      </attribute>
    +
    +      <attribute name="warnOnSessionAttributeFilterFailure" required="false">
    +        <p>If <strong>sessionAttributeNameFilter</strong> or
    +        <strong>sessionAttributeValueClassNameFilter</strong> blocks an
    +        attribute, should this be logged at <code>WARN</code> level? If
    +        <code>WARN</code> level logging is disabled then it will be logged at
    +        <code>DEBUG</code>. The default value of this attribute is
    +        <code>false</code>.</p>
           </attribute>
         </attributes>
     
    @@ -266,16 +285,36 @@
             <code>org.apache.catalina.session.StandardManager</code> class.
             </p>
           </attribute>
    +      
           <attribute name="sessionAttributeNameFilter" required="false">
    -        A regular expression used to filter which session attributes will be
    +        <p>A regular expression used to filter which session attributes will be
             distributed. An attribute will only be distributed if its name matches
             this pattern. If the pattern is zero length or <code>null</code>, all
             attributes are eligible for distribution. The pattern is anchored so the
             session attribute name must fully match the pattern. As an example, the
             value <code>(userName|sessionHistory)</code> will only distribute the
             two session attributes named <code>userName</code> and
             <code>sessionHistory</code>. If not specified, the default value of
    -        <code>null</code> will be used.
    +        <code>null</code> will be used.</p>
    +      </attribute>
    +
    +      <attribute name="sessionAttributeValueClassNameFilter" required="false">
    +        <p>A regular expression used to filter which session attributes will be
    +        distributed. An attribute will only be distributed if the implementation
    +        class name of the value matches this pattern. If the pattern is zero
    +        length or <code>null</code>, all attributes are eligible for
    +        distribution. The pattern is anchored so the fully qualified class name
    +        must fully match the pattern. If not specified, the default value of
    +        <code>null</code> will be used.</p>
    +      </attribute>
    +
    +      <attribute name="warnOnSessionAttributeFilterFailure" required="false">
    +        <p>If <strong>sessionAttributeNameFilter</strong> or
    +        <strong>sessionAttributeValueClassNameFilter</strong> blocks an
    +        attribute, should this be logged at <code>WARN</code> level? If
    +        <code>WARN</code> level logging is disabled then it will be logged at
    +        <code>DEBUG</code>. The default value of this attribute is
    +        <code>false</code>.</p>
           </attribute>
         </attributes>
     
    
50f1b1da794c

Expand the session attribute filtering options

https://github.com/apache/tomcatMark ThomasJan 18, 2016via ghsa
7 files changed · +256 18
  • java/org/apache/catalina/ha/session/mbeans-descriptors.xml+16 0 modified
    @@ -314,6 +314,14 @@
           name="sessionAttributeNameFilter"
           descritpion="The string pattern used for including session attributes in replication. Null means all attributes are included."
           type="java.lang.String"/>
    +    <attribute
    +      name="sessionAttributeValueClassNameFilter"
    +      description="The regular expression used to filter session attributes based on the implementation class of the value. The regular expression is anchored and must match the fully qualified class name."
    +      type="java.lang.String"/>
    +    <attribute
    +      name="warnOnSessionAttributeFilterFailure"
    +      description="Should a WARN level log message be generated if a session attribute fails to match sessionAttributeNameFilter or sessionAttributeClassNameFilter?"
    +      type="boolean"/>
         <operation
           name="expireSession"
           description="Expired the given session"
    @@ -536,6 +544,14 @@
           name="sessionAttributeNameFilter"
           descritpion="The string pattern used for including session attributes in replication. Null means all attributes are included."
           type="java.lang.String"/>
    +    <attribute
    +      name="sessionAttributeValueClassNameFilter"
    +      description="The regular expression used to filter session attributes based on the implementation class of the value. The regular expression is anchored and must match the fully qualified class name."
    +      type="java.lang.String"/>
    +    <attribute
    +      name="warnOnSessionAttributeFilterFailure"
    +      description="Should a WARN level log message be generated if a session attribute fails to match sessionAttributeNameFilter or sessionAttributeClassNameFilter?"
    +      type="boolean"/>
         <operation
           name="expireSession"
           description="Expired the given session"
    
  • java/org/apache/catalina/session/LocalStrings.properties+2 0 modified
    @@ -32,6 +32,8 @@ JDBCStore.missingDataSourceName=No valid JNDI name was given.
     JDBCStore.commitSQLException=SQLException committing connection before closing
     managerBase.container.noop=Managers added to containers other than Contexts will never be used
     managerBase.createSession.ise=createSession: Too many active sessions
    +managerBase.sessionAttributeNameFilter=Skipped session attribute named [{0}] because it did not match the name filter [{1}]
    +managerBase.sessionAttributeValueClassNameFilter=Skipped session attribute named [{0}] because the value type [{1}] did not match the filter [{2}]
     managerBase.sessionTimeout=Invalid session timeout setting {0}
     standardManager.loading=Loading persisted sessions from {0}
     standardManager.loading.exception=Exception while loading persisted sessions
    
  • java/org/apache/catalina/session/ManagerBase.java+116 3 modified
    @@ -203,6 +203,10 @@ public abstract class ManagerBase extends LifecycleMBeanBase
     
         private Pattern sessionAttributeNamePattern;
     
    +    private Pattern sessionAttributeValueClassNamePattern;
    +
    +    private boolean warnOnSessionAttributeFilterFailure;
    +
     
         // ------------------------------------------------------------- Properties
     
    @@ -257,6 +261,86 @@ protected Pattern getSessionAttributeNamePattern() {
         }
     
     
    +    /**
    +     * Obtain the regular expression used to filter session attribute based on
    +     * the implementation class of the value. The regular expression is anchored
    +     * and must match the fully qualified class name.
    +     *
    +     * @return The regular expression currently used to filter class names.
    +     *         {@code null} means no filter is applied. If an empty string is
    +     *         specified then no names will match the filter and all attributes
    +     *         will be blocked.
    +     */
    +    public String getSessionAttributeValueClassNameFilter() {
    +        if (sessionAttributeValueClassNamePattern == null) {
    +            return null;
    +        }
    +        return sessionAttributeValueClassNamePattern.toString();
    +    }
    +
    +
    +    /**
    +     * Provides {@link #getSessionAttributeValueClassNameFilter()} as a
    +     * pre-compiled regular expression pattern.
    +     *
    +     * @return The pre-compiled pattern used to filter session attributes based
    +     *         on the implementation class name of the value. {@code null} means
    +     *         no filter is applied.
    +     */
    +    protected Pattern getSessionAttributeValueClassNamePattern() {
    +        return sessionAttributeValueClassNamePattern;
    +    }
    +
    +
    +    /**
    +     * Set the regular expression to use to filter classes used for session
    +     * attributes. The regular expression is anchored and must match the fully
    +     * qualified class name.
    +     *
    +     * @param sessionAttributeValueClassNameFilter The regular expression to use
    +     *            to filter session attributes based on class name. Use {@code
    +     *            null} if no filtering is required. If an empty string is
    +     *           specified then no names will match the filter and all
    +     *           attributes will be blocked.
    +     *
    +     * @throws PatternSyntaxException If the expression is not valid
    +     */
    +    public void setSessionAttributeValueClassNameFilter(String sessionAttributeValueClassNameFilter)
    +            throws PatternSyntaxException {
    +        if (sessionAttributeValueClassNameFilter == null ||
    +                sessionAttributeValueClassNameFilter.length() == 0) {
    +            sessionAttributeValueClassNamePattern = null;
    +        }
    +        sessionAttributeValueClassNamePattern =
    +                Pattern.compile(sessionAttributeValueClassNameFilter);
    +    }
    +
    +
    +    /**
    +     * Should a warn level log message be generated if a session attribute is
    +     * not persisted / replicated / restored.
    +     *
    +     * @return {@code true} if a warn level log message should be generated
    +     */
    +    public boolean getWarnOnSessionAttributeFilterFailure() {
    +        return warnOnSessionAttributeFilterFailure;
    +    }
    +
    +
    +    /**
    +     * Configure whether or not a warn level log message should be generated if
    +     * a session attribute is not persisted / replicated / restored.
    +     *
    +     * @param warnOnSessionAttributeFilterFailure {@code true} if the
    +     *            warn level message should be generated
    +     *
    +     */
    +    public void setWarnOnSessionAttributeFilterFailure(
    +            boolean warnOnSessionAttributeFilterFailure) {
    +        this.warnOnSessionAttributeFilterFailure = warnOnSessionAttributeFilterFailure;
    +    }
    +
    +
         @Override
         public Context getContext() {
             return context;
    @@ -719,10 +803,39 @@ protected void changeSessionId(Session session, String newId,
         @Override
         public boolean willAttributeDistribute(String name, Object value) {
             Pattern sessionAttributeNamePattern = getSessionAttributeNamePattern();
    -        if (sessionAttributeNamePattern == null) {
    -            return true;
    +        if (sessionAttributeNamePattern != null) {
    +            if (!sessionAttributeNamePattern.matcher(name).matches()) {
    +                if (getWarnOnSessionAttributeFilterFailure() || log.isDebugEnabled()) {
    +                    String msg = sm.getString("managerBase.sessionAttributeNameFilter",
    +                            name, sessionAttributeNamePattern);
    +                    if (getWarnOnSessionAttributeFilterFailure()) {
    +                        log.warn(msg);
    +                    } else {
    +                        log.debug(msg);
    +                    }
    +                }
    +                return false;
    +            }
    +        }
    +
    +        Pattern sessionAttributeValueClassNamePattern = getSessionAttributeValueClassNamePattern();
    +        if (value != null && sessionAttributeValueClassNamePattern != null) {
    +            if (!sessionAttributeValueClassNamePattern.matcher(
    +                    value.getClass().getName()).matches()) {
    +                if (getWarnOnSessionAttributeFilterFailure() || log.isDebugEnabled()) {
    +                    String msg = sm.getString("managerBase.sessionAttributeValueClassNameFilter",
    +                            name, value.getClass().getName(), sessionAttributeNamePattern);
    +                    if (getWarnOnSessionAttributeFilterFailure()) {
    +                        log.warn(msg);
    +                    } else {
    +                        log.debug(msg);
    +                    }
    +                }
    +                return false;
    +            }
             }
    -        return sessionAttributeNamePattern.matcher(name).matches();
    +
    +        return true;
         }
     
     
    
  • java/org/apache/catalina/session/mbeans-descriptors.xml+16 0 modified
    @@ -132,6 +132,14 @@
               descritpion="The string pattern used for including session attributes in distribution. Null means all attributes are included."
                      type="java.lang.String"/>
     
    +    <attribute   name="sessionAttributeValueClassNameFilter"
    +          description="The regular expression used to filter session attributes based on the implementation class of the value. The regular expression is anchored and must match the fully qualified class name."
    +                 type="java.lang.String"/>
    +
    +    <attribute   name="warnOnSessionAttributeFilterFailure"
    +          description="Should a WARN level log message be generated if a session attribute fails to match sessionAttributeNameFilter or sessionAttributeClassNameFilter?"
    +                 type="boolean"/>
    +
         <operation   name="backgroundProcess"
               description="Invalidate all sessions that have expired."
                    impact="ACTION"
    @@ -319,6 +327,14 @@
               descritpion="The string pattern used for including session attributes in distribution. Null means all attributes are included."
                      type="java.lang.String"/>
     
    +    <attribute   name="sessionAttributeValueClassNameFilter"
    +          description="The regular expression used to filter session attributes based on the implementation class of the value. The regular expression is anchored and must match the fully qualified class name."
    +                 type="java.lang.String"/>
    +
    +    <attribute   name="warnOnSessionAttributeFilterFailure"
    +          description="Should a WARN level log message be generated if a session attribute fails to match sessionAttributeNameFilter or sessionAttributeClassNameFilter?"
    +                 type="boolean"/>
    +
         <operation   name="backgroundProcess"
               description="Invalidate all sessions that have expired."
                    impact="ACTION"
    
  • webapps/docs/changelog.xml+7 0 modified
    @@ -207,6 +207,13 @@
             well as unload to ensure that configuration changes made while the web
             application is stopped are applied to any persisted data. (markt)
           </add>
    +      <add>
    +        Extend the session attribute filtering options to include filtering
    +        based on the implementation class of the value and optional
    +        <code>WARN</code> level logging if an attribute is filtered. These
    +        options are avaialble for all of the Manager implementations that ship
    +        with Tomcat. (markt)
    +      </add>
         </changelog>
       </subsection>
       <subsection name="Coyote">
    
  • webapps/docs/config/cluster-manager.xml+56 11 modified
    @@ -77,17 +77,6 @@
             when session attributes are being replicated or removed across Tomcat
             nodes in the cluster.
           </attribute>
    -      <attribute name="sessionAttributeNameFilter" required="false">
    -        A regular expression used to filter which session attributes will be
    -        replicated. An attribute will only be replicated if its name matches
    -        this pattern. If the pattern is zero length or <code>null</code>, all
    -        attributes are eligible for replication. The pattern is anchored so the
    -        session attribute name must fully match the pattern. As an example, the
    -        value <code>(userName|sessionHistory)</code> will only replicate the
    -        two session attributes named <code>userName</code> and
    -        <code>sessionHistory</code>. If not specified, the default value of
    -        <code>null</code> will be used.
    -      </attribute>
           <attribute name="maxInactiveInterval" required="false">
             <p>The initial maximum time interval, in seconds,
             between client requests before a session is invalidated. A negative value
    @@ -184,6 +173,26 @@
             effective only when <code>sendAllSessions</code> is <code>false</code>.
             Default is <code>2000</code> milliseconds.
           </attribute>
    +      <attribute name="sessionAttributeNameFilter" required="false">
    +        <p>A regular expression used to filter which session attributes will be
    +        replicated. An attribute will only be replicated if its name matches
    +        this pattern. If the pattern is zero length or <code>null</code>, all
    +        attributes are eligible for replication. The pattern is anchored so the
    +        session attribute name must fully match the pattern. As an example, the
    +        value <code>(userName|sessionHistory)</code> will only replicate the
    +        two session attributes named <code>userName</code> and
    +        <code>sessionHistory</code>. If not specified, the default value of
    +        <code>null</code> will be used.</p>
    +      </attribute>
    +      <attribute name="sessionAttributeValueClassNameFilter" required="false">
    +        <p>A regular expression used to filter which session attributes will be
    +        replicated. An attribute will only be replicated if the implementation
    +        class name of the value matches this pattern. If the pattern is zero
    +        length or <code>null</code>, all attributes are eligible for
    +        replication. The pattern is anchored so the fully qualified class name
    +        must fully match the pattern. If not specified, the default value of
    +        <code>null</code> will be used.</p>
    +      </attribute>
           <attribute name="stateTimestampDrop" required="false">
             When this node sends a <code>GET_ALL_SESSIONS</code> message to other
             node, all session messages that are received as a response are queued.
    @@ -195,6 +204,14 @@
             If set to <code>false</code>, all queued session messages are handled.
             Default is <code>true</code>.
           </attribute>
    +      <attribute name="warnOnSessionAttributeFilterFailure" required="false">
    +        <p>If <strong>sessionAttributeNameFilter</strong> or
    +        <strong>sessionAttributeValueClassNameFilter</strong> blocks an
    +        attribute, should this be logged at <code>WARN</code> level? If
    +        <code>WARN</code> level logging is disabled then it will be logged at
    +        <code>DEBUG</code>. The default value of this attribute is
    +        <code>false</code>.</p>
    +      </attribute>
         </attributes>
       </subsection>
       <subsection name="org.apache.catalina.ha.session.BackupManager Attributes">
    @@ -218,13 +235,41 @@
             another map.
             Default value is <code>15000</code> milliseconds.
           </attribute>
    +      <attribute name="sessionAttributeNameFilter" required="false">
    +        <p>A regular expression used to filter which session attributes will be
    +        replicated. An attribute will only be replicated if its name matches
    +        this pattern. If the pattern is zero length or <code>null</code>, all
    +        attributes are eligible for replication. The pattern is anchored so the
    +        session attribute name must fully match the pattern. As an example, the
    +        value <code>(userName|sessionHistory)</code> will only replicate the
    +        two session attributes named <code>userName</code> and
    +        <code>sessionHistory</code>. If not specified, the default value of
    +        <code>null</code> will be used.</p>
    +      </attribute>
    +      <attribute name="sessionAttributeValueClassNameFilter" required="false">
    +        <p>A regular expression used to filter which session attributes will be
    +        replicated. An attribute will only be replicated if the implementation
    +        class name of the value matches this pattern. If the pattern is zero
    +        length or <code>null</code>, all attributes are eligible for
    +        replication. The pattern is anchored so the fully qualified class name
    +        must fully match the pattern. If not specified, the default value of
    +        <code>null</code> will be used.</p>
    +      </attribute>
           <attribute name="terminateOnStartFailure" required="false">
             Set to true if you wish to terminate replication map when replication
             map fails to start. If replication map is terminated, associated context
             will fail to start. If you set this attribute to false, replication map
             does not end. It will try to join the map membership in the heartbeat.
             Default value is <code>false</code> .
           </attribute>
    +      <attribute name="warnOnSessionAttributeFilterFailure" required="false">
    +        <p>If <strong>sessionAttributeNameFilter</strong> or
    +        <strong>sessionAttributeValueClassNameFilter</strong> blocks an
    +        attribute, should this be logged at <code>WARN</code> level? If
    +        <code>WARN</code> level logging is disabled then it will be logged at
    +        <code>DEBUG</code>. The default value of this attribute is
    +        <code>false</code>.</p>
    +      </attribute>
         </attributes>
       </subsection>
     </section>
    
  • webapps/docs/config/manager.xml+43 4 modified
    @@ -167,15 +167,34 @@
           </attribute>
     
           <attribute name="sessionAttributeNameFilter" required="false">
    -        A regular expression used to filter which session attributes will be
    +        <p>A regular expression used to filter which session attributes will be
             distributed. An attribute will only be distributed if its name matches
             this pattern. If the pattern is zero length or <code>null</code>, all
             attributes are eligible for distribution. The pattern is anchored so the
             session attribute name must fully match the pattern. As an example, the
             value <code>(userName|sessionHistory)</code> will only distribute the
             two session attributes named <code>userName</code> and
             <code>sessionHistory</code>. If not specified, the default value of
    -        <code>null</code> will be used.
    +        <code>null</code> will be used.</p>
    +      </attribute>
    +
    +      <attribute name="sessionAttributeValueClassNameFilter" required="false">
    +        <p>A regular expression used to filter which session attributes will be
    +        distributed. An attribute will only be distributed if the implementation
    +        class name of the value matches this pattern. If the pattern is zero
    +        length or <code>null</code>, all attributes are eligible for
    +        distribution. The pattern is anchored so the fully qualified class name
    +        must fully match the pattern. If not specified, the default value of
    +        <code>null</code> will be used.</p>
    +      </attribute>
    +
    +      <attribute name="warnOnSessionAttributeFilterFailure" required="false">
    +        <p>If <strong>sessionAttributeNameFilter</strong> or
    +        <strong>sessionAttributeValueClassNameFilter</strong> blocks an
    +        attribute, should this be logged at <code>WARN</code> level? If
    +        <code>WARN</code> level logging is disabled then it will be logged at
    +        <code>DEBUG</code>. The default value of this attribute is
    +        <code>false</code>.</p>
           </attribute>
         </attributes>
     
    @@ -266,16 +285,36 @@
             <code>org.apache.catalina.session.StandardManager</code> class.
             </p>
           </attribute>
    +      
           <attribute name="sessionAttributeNameFilter" required="false">
    -        A regular expression used to filter which session attributes will be
    +        <p>A regular expression used to filter which session attributes will be
             distributed. An attribute will only be distributed if its name matches
             this pattern. If the pattern is zero length or <code>null</code>, all
             attributes are eligible for distribution. The pattern is anchored so the
             session attribute name must fully match the pattern. As an example, the
             value <code>(userName|sessionHistory)</code> will only distribute the
             two session attributes named <code>userName</code> and
             <code>sessionHistory</code>. If not specified, the default value of
    -        <code>null</code> will be used.
    +        <code>null</code> will be used.</p>
    +      </attribute>
    +
    +      <attribute name="sessionAttributeValueClassNameFilter" required="false">
    +        <p>A regular expression used to filter which session attributes will be
    +        distributed. An attribute will only be distributed if the implementation
    +        class name of the value matches this pattern. If the pattern is zero
    +        length or <code>null</code>, all attributes are eligible for
    +        distribution. The pattern is anchored so the fully qualified class name
    +        must fully match the pattern. If not specified, the default value of
    +        <code>null</code> will be used.</p>
    +      </attribute>
    +
    +      <attribute name="warnOnSessionAttributeFilterFailure" required="false">
    +        <p>If <strong>sessionAttributeNameFilter</strong> or
    +        <strong>sessionAttributeValueClassNameFilter</strong> blocks an
    +        attribute, should this be logged at <code>WARN</code> level? If
    +        <code>WARN</code> level logging is disabled then it will be logged at
    +        <code>DEBUG</code>. The default value of this attribute is
    +        <code>false</code>.</p>
           </attribute>
         </attributes>
     
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

71

News mentions

0

No linked articles in our index yet.