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>

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章