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-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org