Thursday 19 September 2013

Sencha Touch 2, CSRF & Double Submit Cookies to protect stateless REST-Services

To protect my stateless REST services against Cross Site Request Forgery attacks I use Double Submit Cookies. For more information on this see http://appsandsecurity.blogspot.de/2012/01/stateless-csrf-protection.html

On the (Java) server side I wrote a servlet filter which checks that the cookie value and the HTTP header are specified and that they match:



public class CsrfDoubleSubmitCookieFilter implements Filter {
 private static final Logger logger = LoggerFactory.getLogger(CsrfDoubleSubmitCookieFilter.class);
 private static final String ANTI_CSRF_DOUBLE_SUBMIT_COOKIE = "Anti-Csrf-Double-Submit-Cookie";

 @Override
 public void destroy() {
 }

 @Override
 public void init(FilterConfig config) throws ServletException {
 }

 @Override
 public void doFilter(ServletRequest servletRequest, ServletResponse response, FilterChain chain) throws IOException, ServletException {
  HttpServletRequest request = (HttpServletRequest) servletRequest;
  String antiCsrfHeader = request.getHeader(ANTI_CSRF_DOUBLE_SUBMIT_COOKIE);
  String antiCsrfCookie = getCookie(request.getCookies(), ANTI_CSRF_DOUBLE_SUBMIT_COOKIE);

  logger.debug("Checking CSRF cookie & header for: {}{}", request.getServletPath(), request.getPathInfo());
  checkCrsfToken(antiCsrfHeader, antiCsrfCookie);

  chain.doFilter(request, response);
 }

 /**
  * Checks that the antiCsrfHeader and the antiCsrfCookie are not blank and do match
  */
 private void checkCrsfToken(String antiCsrfHeader, String antiCsrfCookie) {
  if (StringUtils.isBlank(antiCsrfHeader) || StringUtils.isBlank(antiCsrfCookie) || !antiCsrfHeader.equals(antiCsrfCookie)) {
   logger.error("POSSIBLE CSRF (Cross-Site-Request-Forgery) ATTACK !!! -> Anti-Csrf-Header {} and Anti-Csrf-Cookie {} do not match",
     antiCsrfHeader, antiCsrfCookie);
   throw new WebSecurityException("");
  }
 }

 private String getCookie(Cookie[] cookies, String name) {
  String retVal = null;
  for (int i = 0; i < cookies.length; i++) {
   if (name.endsWith(cookies[i].getName())) {
    retVal = cookies[i].getValue();
    break;
   }
  }
  return retVal;
 }
}



On the Senche Touch side I added a 'beforerequest' listener function to Ext.Ajax in the launch-method of app.js. This will add a new CSRF token to the Anti-CSRF Cookie and Header for each Ajax request



  launch: function () {
        "use strict";
        // Destroy the #appLoadingIndicator element
        Ext.fly('appLoadingIndicator').destroy();
        //Add double submit anti-CSRF cookie and header to each Ajax-request

        Ext.Ajax.addListener( 'beforerequest', function(conn, options, eOpts) {  
          "use strict";
          var csrfToken = Math.random();
          document.cookie='Anti-Csrf-Double-Submit-Cookie=' + csrfToken + ';path=/';
          options.headers['Anti-Csrf-Double-Submit-Cookie'] =csrfToken;
        }

    },


Note: When you keep state on the server side it is obviously more secure to let the server generate a new CSRF token for each user session and put it in the cookie. The client has to read the cookie value and set it into the header for each request. The server can than compare the CSRF token value from the server side session with the header value provided by the client.

No comments:

Post a Comment