AppScan安全扫描:CSRF 攻击 将可能干扰 CSRF 攻击的 HTTP 头除去,并使用伪造的 URL 设置 Referer 头

如果一个网站是从其他网站点击后跳转过来,则会在当前的请求头中带上Referer信息,该信息指示的是跳转前网页的地址信息,而AppScan安全扫描则会报CSRF 攻击;解决方法:通过拦截器,获取请求中的Referer信息,对Referer信息做过滤处理

1、拦截器信息

可用过csrf-ignore-uri配置白单信息,指定相应的URI信息不做拦截

package cn.com.zwjp.framework.web.filter;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import cn.com.zwjp.framework.util.Configuration;
import cn.com.zwjp.framework.web.filter.util.IgnoreUriUtil;

/**
 * CSRF 攻击 将可能干扰 CSRF 攻击的 HTTP 头除去,并使用伪造的 URL 设置 Referer 头
 * 
 * @author 爱兰河少
 * @data 2017年7月6日
 * cn.com.zwjp.framework.web.filter
 */
public class RefererFilter implements Filter {

	private static final Logger logger = LoggerFactory.getLogger(RefererFilter.class);
	
	private List<String> bdUris = null;

	public void init(FilterConfig filterConfig) throws ServletException {
		String ignoreUri = filterConfig.getInitParameter("csrf-ignore-uri");
		if (ignoreUri != null) {
			bdUris = Arrays.asList(ignoreUri.toLowerCase().trim().split(","));
		}
		if (bdUris == null || bdUris.size() <= 0) {
			ignoreUri = Configuration.getInstance().getValue("csrf-ignore-uri");
			if (StringUtils.isNotBlank(ignoreUri)) {
				bdUris = Arrays.asList(ignoreUri.toLowerCase().trim().split(","));
			}
		}
	}

	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest httpServletRequest = (HttpServletRequest) request;
		String rawReferer = httpServletRequest.getHeader("referer");
		// referer白单处理
		boolean legal = IgnoreUriUtil.isIgnoreUri(httpServletRequest);
		//白单
		legal=!legal?IgnoreUriUtil.isIgnoreUri(bdUris, httpServletRequest):legal;
		
		if (!legal) {
			logger.warn(" illegal referer: {}", rawReferer);
			String rootCauseMessage = "可能存在跨域攻击的风险:" + rawReferer;
			// 向后台打印出错信息
			logger.error(rootCauseMessage);
			RequestDispatcher rd = httpServletRequest.getRequestDispatcher("/404.jsp"); // 定向的页面
			rd.forward(request, response);
		} else {
			chain.doFilter(request, response);
		}
	}

	public void destroy() {
		logger.debug(" destroy {} but actually do nothing.", getClass().getName());
	}
}

2、白单验证工具类

因考虑到服务体验,应在不重启服务的情况下实现白单信息的添加,因此采用数据库配置+文件配置结合的形式处理白单信息,为避免拦截器频繁的数据库读取,因此采用数据库白单信息存放于内存中,每隔3个小时时第一个请求则重新读取一次白单信息

package cn.com.zwjp.framework.web.filter.util;

import java.net.MalformedURLException;
import java.net.URL;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import cn.com.zwjp.portal.service.LoginDataService;
import cn.com.zwjp.portal.utils.BaseUtil;

/**
 * 白单信息处理
 * 
 * @author 爱兰河少
 * @data 2018年8月17日 cn.com.zwjp.framework.filter.service.IgnoreUriUtil
 */
public class IgnoreUriUtil {

	private static final Logger log = LoggerFactory.getLogger(IgnoreUriUtil.class);

	private static final int IGNORE_URI_TIMEOUT = 60 * 60 * 3;// 超时时间 3小时,此处为秒

	/**
	 * referer跨站点的脚本编制白单
	 */
	public static Map<List<String>, String> CSRF_IGNORE_URI = new HashMap<List<String>, String>();
	
	/**
	 * 传入配置白单验证
	 * @title
	 * @date 2018年10月9日
	 * @author 爱兰河少
	 * @param bdUris
	 * @param request
	 * @return
	 */
	public static boolean isIgnoreUri(List<String> bdUris,ServletRequest request) {
		boolean legal = false;
		if (bdUris!=null) {
			HttpServletRequest httpServletRequest = (HttpServletRequest) request;
			String rawReferer = httpServletRequest.getHeader("referer");
			if (StringUtils.isNotBlank(rawReferer)) {
				// 获取refer的Host信息
				String referHost = getHost(rawReferer, false);
				if (bdUris.contains(referHost.toLowerCase())) {
					legal = true;
				}
			}
		}
		return legal;
	}

	public static boolean isIgnoreUri(ServletRequest request) {
		boolean legal = false;

		HttpServletRequest httpServletRequest = (HttpServletRequest) request;
		String rawReferer = httpServletRequest.getHeader("referer");
		if (StringUtils.isNotBlank(rawReferer)) {
			// 获取refer的Host信息
			String referHost = getHost(rawReferer, false);

			// 获取服务器的Host信息
			String serverHost = request.getServerName();
			serverHost = StringUtils.isNotBlank(serverHost) ? serverHost.toLowerCase() : serverHost;

			// 获取本地的Host信息,方便开发人员测试
			String localIp = httpServletRequest.getLocalAddr();
			localIp = StringUtils.isNotBlank(localIp) ? localIp.toLowerCase() : localIp;
			if (StringUtils.equals(serverHost, referHost)) {
				legal = true;
			} else if (StringUtils.equals("localhost", referHost) || StringUtils.equals("127.0.0.1", referHost)
					|| StringUtils.equals(localIp, referHost)) {
				legal = true;
			} else {
				legal = isIgnoreUri(referHost);
			}

		} else {
			legal = true;
		}
		return legal;
	}

	/**
	 * 是否在白单,当referHost为空是则认为是在白单信息中
	 * 
	 * @title
	 * @date 2018年8月17日
	 * @author 爱兰河少
	 * @param referHost
	 * @return
	 */
	public static boolean isIgnoreUri(String referHost) {
		boolean legal = false;
		if (StringUtils.isNotBlank(referHost)) {
			boolean newIgnore = false;
			List<String> bdUris = null;
			if (CSRF_IGNORE_URI.size() > 0) {
				String time = null;
				for (Map.Entry<List<String>, String> entry : CSRF_IGNORE_URI.entrySet()) {
					bdUris = entry.getKey();
					time = entry.getValue();
					break;
				}
				// 是否未超时
				if (isTimeout(time)) {
					newIgnore = true;
					bdUris = null;
				}
			} else {
				// 当为空时设置值
				newIgnore = true;
			}
			if (newIgnore) {
				bdUris = getBdUris();
				// 清空Map
				CSRF_IGNORE_URI.clear();
				// 设置值
				CSRF_IGNORE_URI.put(bdUris, getDate_yyyy_MM_dd_HH_mm_ss());
			}
			if (bdUris.contains(referHost.toLowerCase())) {
				legal = true;
			}else{
				if(StringUtils.equals("localhost", referHost) || StringUtils.equals("127.0.0.1", referHost)){
					legal = true;
				}
			}
		} else {
			legal = true;
		}
		return legal;
	}

	/**
	 * 
	 * @title
	 * @date 2018年8月17日
	 * @author 爱兰河少
	 * @param request
	 * @return
	 */
	public static boolean isFilterCountSuffix(ServletRequest request) {
		boolean legal = false;
		// 转换类型
		HttpServletRequest req = (HttpServletRequest) request;
		String uri = req.getRequestURI();
		String suffix = getUrlSuffix(uri);
		if ("js".equals(suffix) || "css".equals(suffix) || "jpg".equals(suffix) || "png".equals(suffix)
				|| "gif".equals(suffix)) {
			legal = true;
		}
		return legal;
	}

	/**
	 * 获取链接的后缀名
	 * 
	 * @return
	 */
	private static String getUrlSuffix(String url) {
		String suffix = "";
		if (StringUtils.isNotBlank(url)) {
			int index = url.lastIndexOf(".");
			if (index > 0) {
				suffix = url.substring(index + 1);
			}
			String[] spEndUrl = suffix.split("\\?");
			suffix = spEndUrl.length > 1 ? spEndUrl[0] : suffix;
			spEndUrl = suffix.split("\\,");
			suffix = spEndUrl.length > 1 ? spEndUrl[0] : suffix;
			spEndUrl = suffix.split("\\;");
			suffix = spEndUrl.length > 1 ? spEndUrl[0] : suffix;
			suffix = suffix.toLowerCase();
		}
		return suffix;
	}

	/**
	 * 获取白单Host信息
	 * 
	 * @title
	 * @date 2018年8月17日
	 * @author 爱兰河少
	 * @return
	 */
	private static List<String> getBdUris() {
		List<String> bdUris = new ArrayList<String>();
		List<Map<String, Object>> list = getIgnoreUri();
		for (Map map : list) {
			String v_url = MapUtils.getString(map, "V_URL");
			String urlHost = getHost(v_url, true);
			if (StringUtils.isNotBlank(urlHost)) {
				bdUris.add(urlHost.toLowerCase());
			}
		}
		return bdUris;
	}

	/**
	 * 获取url字符串的host
	 * 
	 * @title
	 * @date 2018年8月17日
	 * @author 爱兰河少
	 * @param v_url
	 * @param addUrl
	 *            当url地址不包含http://、https://、ftp://时添加http://前缀
	 * @return
	 */
	public static String getHost(String v_url, boolean addUrl) {
		String host = null;
		try {
			if (addUrl && !v_url.toLowerCase().startsWith("http://") && !v_url.toLowerCase().startsWith("https://")
					&& !v_url.toLowerCase().startsWith("ftp://")) {
				log.info("url读取Host白单信息时,url信息不符合规范:{}", v_url);
				v_url = "http://" + v_url;
			}
			URL url = new URL(v_url);
			host = url.getHost();
		} catch (MalformedURLException e) {
			log.error("Host白单读取出错:{}", e);
		}
		return host;
	}

	/**
	 * 是否超时
	 * 
	 * @title
	 * @date 2018年8月17日
	 * @author 爱兰河少
	 * @param time
	 * @return
	 */
	private static boolean isTimeout(String time) {
		String newTime = getDate_yyyy_MM_dd_HH_mm_ss();
		long mm = getMiaoCha(newTime, time);
		int max = IGNORE_URI_TIMEOUT;
		if (mm <= max) {
			return false;
		}
		return true;
	}

	/**
	 * 获取当前时间,格式为:年-月-日 时:分:秒
	 * 
	 * @title
	 * @date 2018年8月17日
	 * @author 爱兰河少
	 * @return
	 */
	private static String getDate_yyyy_MM_dd_HH_mm_ss() {
		return BaseUtil.now_yyyy_MM_dd_HH_mm_ss();
	}

	/**
	 * 计算两个时间差,精确到秒
	 * 
	 * @title
	 * @date 2018年8月17日
	 * @author 爱兰河少
	 * @param newTime
	 * @param oldTime
	 * @return
	 */
	private static long getMiaoCha(String str1, String str2) {
		SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm");
		Date one;
		Date two;
		// long days=0;
		long fzs = 0;
		try {
			one = df.parse(str1);
			two = df.parse(str2);
			long time1 = one.getTime();
			long time2 = two.getTime();
			long diff;
			if (time1 < time2) {
				diff = time2 - time1;
			} else {
				diff = time1 - time2;
			}
			// days = diff / (1000 * 60 * 60 * 24);
			fzs = diff / 1000;
		} catch (ParseException e) {
			e.printStackTrace();
		}
		return fzs;
	}

	/**
	 * 获取数据库Url信息, Map对应的数据里面需有Key为V_URL的信息,次参数对应的值则为白单信息
	 * 
	 * @title
	 * @date 2018年8月17日
	 * @author 爱兰河少
	 * @return
	 */
	private static List<Map<String, Object>> getIgnoreUri() {
		return LoginDataService.getInstance().getIgnoreUri();
	}
}

3、web.xml配置

<filter>
		<filter-name>CookieFilter</filter-name>
		<filter-class>cn.com.zwjp.framework.web.filter.CookieFilter</filter-class>
	</filter>
	<filter-mapping>
		<filter-name>CookieFilter</filter-name>
		<url-pattern>/*</url-pattern>
        <!-- 此处不可加FORWARD,否则Referer信息不在白单时会造成死循环
		<dispatcher>REQUEST</dispatcher>
	</filter-mapping>

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值