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 2010/05/07 19:38:03 UTC

svn commit: r942157 - in /tomcat/trunk: java/org/apache/catalina/filters/Constants.java java/org/apache/catalina/filters/CsrfPreventionFilter.java webapps/docs/config/filter.xml

Author: markt
Date: Fri May  7 17:38:03 2010
New Revision: 942157

URL: http://svn.apache.org/viewvc?rev=942157&view=rev
Log:
Add a simple CSRF prevention filter. It has been tested with the Tomcat 6 manager app and a back-port proposal will follow shortly.

Added:
    tomcat/trunk/java/org/apache/catalina/filters/CsrfPreventionFilter.java   (with props)
Modified:
    tomcat/trunk/java/org/apache/catalina/filters/Constants.java
    tomcat/trunk/webapps/docs/config/filter.xml

Modified: tomcat/trunk/java/org/apache/catalina/filters/Constants.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/filters/Constants.java?rev=942157&r1=942156&r2=942157&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/filters/Constants.java (original)
+++ tomcat/trunk/java/org/apache/catalina/filters/Constants.java Fri May  7 17:38:03 2010
@@ -31,4 +31,9 @@ public final class Constants {
 
     public static final String Package = "org.apache.catalina.filters";
     
+    public static final String CSRF_NONCE_SESSION_ATTR_NAME =
+        "org.apache.catalina.filters.CSRF_NONCE";
+    
+    public static final String CSRF_NONCE_REQUEST_PARAM =
+        "org.apache.catalina.filters.CSRF_NONCE";
 }

Added: tomcat/trunk/java/org/apache/catalina/filters/CsrfPreventionFilter.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/filters/CsrfPreventionFilter.java?rev=942157&view=auto
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/filters/CsrfPreventionFilter.java (added)
+++ tomcat/trunk/java/org/apache/catalina/filters/CsrfPreventionFilter.java Fri May  7 17:38:03 2010
@@ -0,0 +1,190 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.filters;
+
+import java.io.IOException;
+import java.util.Random;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+/**
+ * Provides basic CSRF protection for a web application. The filter assumes
+ * that:
+ * <ul>
+ * <li>The filter is mapped to /*</li>
+ * <li>{@link HttpServletResponse#encodeRedirectURL(String)} and
+ * {@link HttpServletResponse#encodeURL(String)} are used to encode all URLs
+ * returned to the client
+ * </ul>
+ */
+public class CsrfPreventionFilter extends FilterBase {
+
+    private static final Log log =
+        LogFactory.getLog(CsrfPreventionFilter.class);
+    
+    private final Random randomSource = new Random();
+
+    @Override
+    protected Log getLogger() {
+        return log;
+    }
+
+    public void doFilter(ServletRequest request, ServletResponse response,
+            FilterChain chain) throws IOException, ServletException {
+
+        ServletResponse wResponse = null;
+        
+        if (request instanceof HttpServletRequest &&
+                response instanceof HttpServletResponse) {
+            
+            HttpServletRequest req = (HttpServletRequest) request;
+            HttpServletResponse res = (HttpServletResponse) response;
+
+            String previousNonce =
+                req.getParameter(Constants.CSRF_NONCE_REQUEST_PARAM);
+            String expectedNonce = (String) req.getSession(true).getAttribute(
+                    Constants.CSRF_NONCE_SESSION_ATTR_NAME);
+            
+            if (expectedNonce != null && !expectedNonce.equals(previousNonce)) {
+                res.sendError(HttpServletResponse.SC_FORBIDDEN);
+                return;
+            }
+            
+            String newNonce = generateNonce();
+            
+            req.getSession(true).setAttribute(
+                    Constants.CSRF_NONCE_SESSION_ATTR_NAME, newNonce);
+            
+            wResponse = new CsrfResponseWrapper(res, newNonce);
+        } else {
+            wResponse = response;
+        }
+        
+        chain.doFilter(request, wResponse);
+    }
+
+    /**
+     * Generate a once time token (nonce) for authenticating subsequent
+     * requests. This will also add the token to the session. The nonce
+     * generation is a simplified version of ManagerBase.generateSessionId().
+     * 
+     */
+    protected String generateNonce() {
+        byte random[] = new byte[16];
+
+        // Render the result as a String of hexadecimal digits
+        StringBuilder buffer = new StringBuilder();
+
+        randomSource.nextBytes(random);
+       
+        for (int j = 0; j < random.length; j++) {
+            byte b1 = (byte) ((random[j] & 0xf0) >> 4);
+            byte b2 = (byte) (random[j] & 0x0f);
+            if (b1 < 10)
+                buffer.append((char) ('0' + b1));
+            else
+                buffer.append((char) ('A' + (b1 - 10)));
+            if (b2 < 10)
+                buffer.append((char) ('0' + b2));
+            else
+                buffer.append((char) ('A' + (b2 - 10)));
+        }
+
+        return buffer.toString();
+    }
+
+    private static class CsrfResponseWrapper
+            extends HttpServletResponseWrapper {
+
+        private String nonce;
+
+        public CsrfResponseWrapper(HttpServletResponse response, String nonce) {
+            super(response);
+            this.nonce = nonce;
+        }
+
+        @Override
+        @Deprecated
+        public String encodeRedirectUrl(String url) {
+            return encodeRedirectURL(url);
+        }
+
+        @Override
+        public String encodeRedirectURL(String url) {
+            return addNonce(super.encodeRedirectURL(url));
+        }
+
+        @Override
+        @Deprecated
+        public String encodeUrl(String url) {
+            return encodeURL(url);
+        }
+
+        @Override
+        public String encodeURL(String url) {
+            return addNonce(super.encodeURL(url));
+        }
+        
+        /**
+         * Return the specified URL with the nonce added to the query string
+         *
+         * @param url URL to be modified
+         * @param nonce The nonce to add
+         */
+        private String addNonce(String url) {
+
+            if ((url == null) || (nonce == null))
+                return (url);
+
+            String path = url;
+            String query = "";
+            String anchor = "";
+            int question = url.indexOf('?');
+            if (question >= 0) {
+                path = url.substring(0, question);
+                query = url.substring(question);
+            }
+            int pound = path.indexOf('#');
+            if (pound >= 0) {
+                anchor = path.substring(pound);
+                path = path.substring(0, pound);
+            }
+            StringBuilder sb = new StringBuilder(path);
+            sb.append(anchor);
+            if (query.length() >0) {
+                sb.append(query);
+                sb.append('&');
+            } else {
+                sb.append('?');
+            }
+            sb.append(Constants.CSRF_NONCE_REQUEST_PARAM);
+            sb.append('=');
+            sb.append(nonce);
+            return (sb.toString());
+        }
+    }
+}

Propchange: tomcat/trunk/java/org/apache/catalina/filters/CsrfPreventionFilter.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: tomcat/trunk/webapps/docs/config/filter.xml
URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/config/filter.xml?rev=942157&r1=942156&r2=942157&view=diff
==============================================================================
--- tomcat/trunk/webapps/docs/config/filter.xml (original)
+++ tomcat/trunk/webapps/docs/config/filter.xml Fri May  7 17:38:03 2010
@@ -87,6 +87,41 @@
 </section>
 
 
+<section name="CSRF Prevention Filter">
+
+  <subsection name="Introduction">
+
+    <p>This filter provides basic CSRF protection for a web application. The
+    filter assumes that it is mapped to <code>/*</code> and that all URLs
+    returned to the client are encoded via a call to
+    <code>HttpServletResponse#encodeRedirectURL(String)</code> or
+    <code>HttpServletResponse#encodeURL(String)</code>.</p>
+    
+    <p>This filter prevents CSRF by generating a nonce and storing it in the
+    session. URLs are also encoded with the same nonce. When the next request is
+    received the nonce in the request is compared to the nonce in the session
+    and only if they are the same is the request allowed to continue.</p>
+    
+  </subsection>
+
+  <subsection name="Filter Class Name">
+
+    <p>The filter class name for the CSRF Prevention Filter is
+    <strong><code>org.apache.catalina.filters.CsrfPreventionFilter</code>
+    </strong>.</p>
+
+  </subsection>
+
+  <subsection name="Initialisation parameters">
+
+    <p>The CSRF Prevention Filter does not support any initialization
+    parameters.</p>
+
+  </subsection>
+
+</section>
+
+
 <section name="Remote Address Filter">
 
   <subsection name="Introduction">



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