You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by ma...@apache.org on 2022/05/09 17:38:21 UTC

[tomcat] branch 8.5.x updated: Fix BZ 65853 - refactor CsrfPreventionFilter to make extension easier

This is an automated email from the ASF dual-hosted git repository.

markt pushed a commit to branch 8.5.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/8.5.x by this push:
     new 2da3fa89e4 Fix BZ 65853 - refactor CsrfPreventionFilter to make extension easier
2da3fa89e4 is described below

commit 2da3fa89e47694260625df1548ab834823fee080
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Mon May 9 18:34:52 2022 +0100

    Fix BZ 65853 - refactor CsrfPreventionFilter to make extension easier
---
 .../catalina/filters/CsrfPreventionFilter.java     | 93 +++++++++++++++++-----
 .../catalina/filters/CsrfPreventionFilterBase.java | 19 +++++
 .../catalina/filters/RestCsrfPreventionFilter.java |  2 +-
 webapps/docs/changelog.xml                         |  5 ++
 4 files changed, 97 insertions(+), 22 deletions(-)

diff --git a/java/org/apache/catalina/filters/CsrfPreventionFilter.java b/java/org/apache/catalina/filters/CsrfPreventionFilter.java
index fe7a27f92d..6e45f3d111 100644
--- a/java/org/apache/catalina/filters/CsrfPreventionFilter.java
+++ b/java/org/apache/catalina/filters/CsrfPreventionFilter.java
@@ -118,25 +118,11 @@ public class CsrfPreventionFilter extends CsrfPreventionFilterBase {
             HttpServletRequest req = (HttpServletRequest) request;
             HttpServletResponse res = (HttpServletResponse) response;
 
-            boolean skipNonceCheck = false;
-
-            if (Constants.METHOD_GET.equals(req.getMethod())
-                    && entryPoints.contains(getRequestedPath(req))) {
-                if(log.isTraceEnabled()) {
-                    log.trace("Skipping CSRF nonce-check for GET request to entry point " + getRequestedPath(req));
-                }
-
-                skipNonceCheck = true;
-            }
-
             HttpSession session = req.getSession(false);
 
-            @SuppressWarnings("unchecked")
-            LruCache<String> nonceCache = (session == null) ? null
-                    : (LruCache<String>) session.getAttribute(
-                            Constants.CSRF_NONCE_SESSION_ATTR_NAME);
+            NonceCache<String> nonceCache = (session == null) ? null : getNonceCache(req, session);
 
-            if (!skipNonceCheck) {
+            if (!skipNonceCheck(req)) {
                 String previousNonce =
                     req.getParameter(nonceRequestParameterName);
 
@@ -182,7 +168,6 @@ public class CsrfPreventionFilter extends CsrfPreventionFilterBase {
                     log.debug("Creating new CSRF nonce cache with size=" + nonceCacheSize + " for session " + (null == session ? "(will create)" : session.getId()));
                 }
 
-                nonceCache = new LruCache<>(nonceCacheSize);
                 if (session == null) {
                     if(log.isDebugEnabled()) {
                          log.debug("Creating new session to store CSRF nonce cache");
@@ -190,11 +175,11 @@ public class CsrfPreventionFilter extends CsrfPreventionFilterBase {
 
                     session = req.getSession(true);
                 }
-                session.setAttribute(
-                        Constants.CSRF_NONCE_SESSION_ATTR_NAME, nonceCache);
+
+                nonceCache = createNonceCache(req, session);
             }
 
-            String newNonce = generateNonce();
+            String newNonce = generateNonce(req);
 
             nonceCache.add(newNonce);
 
@@ -212,6 +197,64 @@ public class CsrfPreventionFilter extends CsrfPreventionFilterBase {
     }
 
 
+    protected boolean skipNonceCheck(HttpServletRequest request) {
+        if (!Constants.METHOD_GET.equals(request.getMethod())) {
+            return false;
+        }
+
+        String requestedPath = getRequestedPath(request);
+
+        if (!entryPoints.contains(requestedPath)) {
+            return false;
+        }
+
+        if (log.isTraceEnabled()) {
+            log.trace("Skipping CSRF nonce-check for GET request to entry point " + requestedPath);
+        }
+
+        return true;
+    }
+
+
+    /**
+     * Create a new {@link NonceCache} and store in the {@link HttpSession}.
+     * This method is provided primarily for the benefit of sub-classes that
+     * wish to customise this behaviour.
+     *
+     * @param request   The request that triggered the need to create the nonce
+     *                      cache. Unused by the default implementation.
+     * @param session   The session associated with the request.
+     *
+     * @return A newly created {@link NonceCache}
+     */
+    protected NonceCache<String> createNonceCache(HttpServletRequest request, HttpSession session) {
+
+        NonceCache<String> nonceCache = new LruCache<>(nonceCacheSize);
+
+        session.setAttribute(Constants.CSRF_NONCE_SESSION_ATTR_NAME, nonceCache);
+
+        return nonceCache;
+    }
+
+
+    /**
+     * Obtain the {@link NonceCache} associated with the request and/or session.
+     * This method is provided primarily for the benefit of sub-classes that
+     * wish to customise this behaviour.
+     *
+     * @param request   The request that triggered the need to obtain the nonce
+     *                      cache. Unused by the default implementation.
+     * @param session   The session associated with the request.
+     *
+     * @return A newly created {@link NonceCache}
+     */
+    protected NonceCache<String> getNonceCache(HttpServletRequest request, HttpSession session) {
+        @SuppressWarnings("unchecked")
+        NonceCache<String> nonceCache =
+                (NonceCache<String>) session.getAttribute(Constants.CSRF_NONCE_SESSION_ATTR_NAME);
+        return nonceCache;
+    }
+
     protected static class CsrfResponseWrapper
             extends HttpServletResponseWrapper {
 
@@ -285,7 +328,15 @@ public class CsrfPreventionFilter extends CsrfPreventionFilterBase {
         }
     }
 
-    protected static class LruCache<T> implements Serializable {
+
+    protected static interface NonceCache<T> extends Serializable {
+        void add(T nonce);
+
+        boolean contains(T nonce);
+    }
+
+
+    protected static class LruCache<T> implements NonceCache<T> {
 
         private static final long serialVersionUID = 1L;
 
diff --git a/java/org/apache/catalina/filters/CsrfPreventionFilterBase.java b/java/org/apache/catalina/filters/CsrfPreventionFilterBase.java
index 1f51fa6b58..ba35ec42a5 100644
--- a/java/org/apache/catalina/filters/CsrfPreventionFilterBase.java
+++ b/java/org/apache/catalina/filters/CsrfPreventionFilterBase.java
@@ -93,13 +93,32 @@ public abstract class CsrfPreventionFilterBase extends FilterBase {
         return true;
     }
 
+
+    /**
+     * Generate a once time token (nonce) for authenticating subsequent
+     * requests. The nonce generation is a simplified version of
+     * ManagerBase.generateSessionId().
+     *
+     * @param request   The request. Unused in this method but present for the
+     *                  the benefit of sub-classes.
+     *
+     * @return the generated nonce
+     */
+    protected String generateNonce(HttpServletRequest request) {
+        return generateNonce();
+    }
+
     /**
      * Generate a once time token (nonce) for authenticating subsequent
      * requests. The nonce generation is a simplified version of
      * ManagerBase.generateSessionId().
      *
      * @return the generated nonce
+     *
+     * @deprecated Use {@link #generateNonce(HttpServletRequest)} instead. This
+     *             method will be removed in Apache Tomcat 10.1.x onwards.
      */
+    @Deprecated
     protected String generateNonce() {
         byte random[] = new byte[16];
 
diff --git a/java/org/apache/catalina/filters/RestCsrfPreventionFilter.java b/java/org/apache/catalina/filters/RestCsrfPreventionFilter.java
index 66d3ea98f7..eb4afa3a70 100644
--- a/java/org/apache/catalina/filters/RestCsrfPreventionFilter.java
+++ b/java/org/apache/catalina/filters/RestCsrfPreventionFilter.java
@@ -221,7 +221,7 @@ public class RestCsrfPreventionFilter extends CsrfPreventionFilterBase {
                 String nonceFromSessionStr = extractNonceFromSession(request.getSession(false),
                         Constants.CSRF_REST_NONCE_SESSION_ATTR_NAME);
                 if (nonceFromSessionStr == null) {
-                    nonceFromSessionStr = generateNonce();
+                    nonceFromSessionStr = generateNonce(request);
                     storeNonceToSession(Objects.requireNonNull(request.getSession(true)),
                             Constants.CSRF_REST_NONCE_SESSION_ATTR_NAME, nonceFromSessionStr);
                 }
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 27873e6402..743cdc76bd 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -107,6 +107,11 @@
 <section name="Tomcat 8.5.79 (schultz)" rtext="in development">
   <subsection name="Catalina">
     <changelog>
+      <scode>
+        <bug>65853</bug>: Refactor the <code>CsrfPreventionFilter</code> to make
+        it easier for sub-classes to modify the nonce generation and storage.
+        Based on suggestions by Marvin Fröhlich. (markt)
+      </scode>
       <fix>
         <bug>65991</bug>: Avoid NPE with <code>SSLAuthenticator</code> when
         <code>boundOnInit</code> is used on a connector, during the check


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org