com.versatus.jwebshield.filter.SecurityFilter.java Source code

Java tutorial

Introduction

Here is the source code for com.versatus.jwebshield.filter.SecurityFilter.java

Source

/*
 * This file is part of J-WebShield framework.
    
J-WebShield framework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
    
J-WebShield framework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
    
You should have received a copy of the GNU General Public License
along with J-WebShield framework.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.versatus.jwebshield.filter;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.configuration.XMLConfiguration;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.cache.Cache;
import com.versatus.jwebshield.SecurityConstant;
import com.versatus.jwebshield.SecurityInfo;
import com.versatus.jwebshield.UrlExclusionList;

/**
 * Intercepts requests according to mappings defined in web.xml. When applicable
 * checks if the randomized token in the request matches the token name and
 * value in the token cache stored in the session. If session does not exist for
 * a request - ignores the request. If session exists: 1. Check if URL of the
 * page matches exclusion list defined in the filter config. file (see FAQ) -
 * ignore the request. 2. If randomized token from cache does not match the
 * token received with the request - send error 401 and log a message containing
 * IP address and URL. 3. Compare Referer header from the request to the one
 * stored in the cache if not blank - if no match send error 401 and log a
 * message containing IP address and URL.
 * 
 * @author Versatus Corp.
 */
public class SecurityFilter implements Filter {

    private final Logger logger = LoggerFactory.getLogger(SecurityFilter.class);

    private boolean useCsrfToken = false;
    private String csrfHeaderName;
    private String csrfCookieName;
    private String[] methodExclusionList;

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        // Assume its HTTP
        HttpServletRequest httpReq = (HttpServletRequest) request;

        String reqInfo = "J-WebShield Alert: CSRF attack detected! request URL="
                + httpReq.getRequestURL().toString() + "| from IP address=" + httpReq.getRemoteAddr();

        logger.debug("doFilter: IP address=" + httpReq.getRemoteAddr());
        logger.debug("doFilter: pathInfo=" + httpReq.getPathInfo());
        logger.debug("doFilter: queryString=" + httpReq.getQueryString());
        logger.debug("doFilter: requestURL=" + httpReq.getRequestURL().toString());
        logger.debug("doFilter: method=" + httpReq.getMethod());
        logger.debug("doFilter: Origin=" + httpReq.getHeader("Origin"));
        logger.info("doFilter: Referer=" + httpReq.getHeader("Referer"));
        logger.info("doFilter: " + csrfHeaderName + "=" + httpReq.getHeader(csrfHeaderName));

        UrlExclusionList exclList = (UrlExclusionList) request.getServletContext()
                .getAttribute(SecurityConstant.CSRF_CHECK_URL_EXCL_LIST_ATTR_NAME);
        HttpSession session = httpReq.getSession(false);
        if (session == null) {
            chain.doFilter(request, response);
            return;
        }

        logger.debug("doFilter: matching " + httpReq.getRequestURI() + " to exclusions list "
                + exclList.getExclusionMap());

        try {
            if (!exclList.isEmpty() && exclList.isMatch(httpReq.getRequestURI())) {
                chain.doFilter(request, response);
                return;
            }
        } catch (Exception e) {
            logger.error("doFilter", e);
        }
        // check CSRF cookie/header
        boolean csrfHeaderPassed = false;
        String rawCsrfHeaderVal = httpReq.getHeader(csrfHeaderName);
        if (useCsrfToken && StringUtils.isNotBlank(rawCsrfHeaderVal)) {
            String csrfHeader = StringUtils.strip(httpReq.getHeader(csrfHeaderName), "\"");
            logger.debug("doFilter: csrfHeader after decoding" + csrfHeader);
            Cookie[] cookies = httpReq.getCookies();
            for (Cookie c : cookies) {
                String name = c.getName();

                if (StringUtils.isNotBlank(csrfCookieName) && csrfCookieName.equals(name)) {

                    logger.debug("doFilter: cookie domain=" + c.getDomain() + "|name=" + name + "|value="
                            + c.getValue() + "|path=" + c.getPath() + "|maxage=" + c.getMaxAge() + "|httpOnly="
                            + c.isHttpOnly());

                    logger.debug("doFilter: string comp:" + StringUtils.difference(csrfHeader, c.getValue()));

                    if (StringUtils.isNotBlank(csrfHeader) && csrfHeader.equals(c.getValue())) {

                        csrfHeaderPassed = true;
                        logger.info("Header " + csrfHeaderName + " value matches the cookie " + csrfCookieName);
                        break;
                    } else {
                        logger.info(
                                "Header " + csrfHeaderName + " value does not match the cookie " + csrfCookieName);
                    }
                }

            }
            // String csrfCookieVal = (String) session
            // .getAttribute(SecurityConstant.CSRFCOOKIE_VALUE_PARAM);
            // if (csrfCookieVal != null && csrfCookieVal.equals(csrfHeader)) {
            // // chain.doFilter(request, response);
            // // return;
            // csrfHeaderPassed = true;
            // } else {
            // // logger.info(reqInfo);
            // // sendSecurityReject(response);
            // }
        }

        if (useCsrfToken && csrfHeaderPassed) {
            chain.doFilter(request, response);
            return;
        }

        // Validate that the salt is in the cache
        Cache<SecurityInfo, SecurityInfo> csrfPreventionSaltCache = (Cache<SecurityInfo, SecurityInfo>) httpReq
                .getSession().getAttribute(SecurityConstant.SALT_CACHE_ATTR_NAME);

        if (csrfPreventionSaltCache != null) {
            // Get the salt sent with the request
            String saltName = (String) httpReq.getSession().getAttribute(SecurityConstant.SALT_PARAM_NAME);

            logger.debug("doFilter: csrf saltName=" + saltName);

            if (saltName != null) {

                String salt = httpReq.getParameter(saltName);

                logger.debug("doFilter: csrf salt=" + salt);

                if (salt != null) {

                    SecurityInfo si = new SecurityInfo(saltName, salt);

                    logger.debug("doFilter: csrf token=" + csrfPreventionSaltCache.getIfPresent(si));

                    SecurityInfo cachedSi = csrfPreventionSaltCache.getIfPresent(si);
                    if (cachedSi != null) {
                        // csrfPreventionSaltCache.invalidate(si);
                        if (SecurityTokenFilter.checkReferer) {
                            String refHeader = StringUtils.defaultString(httpReq.getHeader("Referer"));
                            logger.debug("doFilter: refHeader=" + refHeader);
                            if (StringUtils.isNotBlank(refHeader)) {
                                try {
                                    URL refUrl = new URL(refHeader);
                                    refHeader = refUrl.getHost();
                                } catch (MalformedURLException mex) {
                                    logger.debug("doFilter: parsing referer header failed", mex);
                                }
                            }
                            if (!cachedSi.getRefererHost().isEmpty()
                                    && !refHeader.equalsIgnoreCase(cachedSi.getRefererHost())) {
                                logger.info("Potential CSRF detected - Referer host does not match orignal! "
                                        + refHeader + " != " + cachedSi.getRefererHost());
                                sendSecurityReject(response);
                            }
                        }

                        chain.doFilter(request, response);
                    } else {
                        logger.info(reqInfo);
                        sendSecurityReject(response);
                    }
                } else if (httpMethodMatch(httpReq.getMethod())) {
                    // let flow through
                    chain.doFilter(request, response);
                } else {
                    logger.info(reqInfo);
                    sendSecurityReject(response);
                }
            }
        } else {
            chain.doFilter(request, response);
        }

    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        String file = filterConfig.getInitParameter("configFile");
        if (file != null) {

            try {
                XMLConfiguration config = new XMLConfiguration(file);

                useCsrfToken = config.getBoolean(SecurityConstant.USECSRFHEADER_PARAM);
                csrfHeaderName = config.getString(SecurityConstant.CSRFHEADERNAME_PARAM);
                csrfCookieName = config.getString(SecurityConstant.CSRFCOOKIENAME_PARAM);
                methodExclusionList = config.getStringArray("httpMethodExclusions");

                logger.info("init: useCsrfToken=" + useCsrfToken);
                logger.info("init: csrfHeaderName=" + csrfHeaderName);
                logger.info("init: httpMethodExclusions=" + Arrays.asList(methodExclusionList));
            } catch (Exception cex) {
                logger.error("init: unable to load configFile " + file, cex);

            }
        } else {
            logger.error("init: No configFile specified");
        }
    }

    @Override
    public void destroy() {
    }

    private void sendSecurityReject(ServletResponse response) {
        try {
            ((HttpServletResponse) response).sendError(HttpServletResponse.SC_UNAUTHORIZED);
        } catch (IOException e) {
            // ignore
        }
    }

    private boolean httpMethodMatch(String method) {
        for (String m : methodExclusionList) {
            if (method != null && method.equalsIgnoreCase(m)) {
                return true;
            }
        }
        return false;
    }
}