先看看跨站請求僞造(CSRF攻擊)理解
一 概念 你這可以這麼理解CSRF攻擊:攻擊者盜用了你的身份,以你的名義發送惡意請求。CSRF能夠做的事情包括:以你名義發送郵件,發消息,盜取你的賬號,甚至於購買商品,虛擬貨幣轉賬......造成的問題包括:個人隱私泄露以及財產安全。
二 過程 1 受害者 Bob 在銀行有一筆存款,通過對銀行的網站發送請求 http://bank.example/withdraw?account=bob&amount=1000000&for=bob2 可以使 Bob 把 1000000 的存款轉到 bob2 的賬號下。
2 通常情況下,該請求發送到網站後,服務器會先驗證該請求是否來自一個合法的 session,並且該 session 的用戶 Bob 已經成功登陸。
3 黑客 Mallory 自己在該銀行也有賬戶,他知道上文中的 URL 可以把錢進行轉帳操作。Mallory 可以自己發送一個請求給銀行:http://bank.example/withdraw?account=bob&amount=1000000&for=Mallory。 但是這個請求來自 Mallory 而非 Bob,他不能通過安全認證,因此該請求不會起作用。
4 這時,Mallory 想到使用 CSRF 的攻擊方式,他先自己做一個網站,在網站中放入如下代碼: src=”http://bank.example/withdraw?account=bob&amount=1000000&for=Mallory ”,並且通過廣告等誘使 Bob 來訪問他的網站。
5 當 Bob 訪問該網站時,上述 url 就會從 Bob 的瀏覽器發向銀行,而這個請求會附帶 Bob 瀏覽器中的 cookie 一起發向銀行服務器。大多數情況下,該請求會失敗,因爲他要求 Bob 的認證信息。
6 但是,如果 Bob 當時恰巧剛訪問他的銀行後不久,他的瀏覽器與銀行網站之間的 session 尚未過期,瀏覽器的 cookie 之中含有 Bob 的認證信息。 這時,悲劇發生了,這個 url 請求就會得到響應,錢將從 Bob 的賬號轉移到 Mallory 的賬號,而 Bob 當時毫不知情。等以後 Bob 發現賬戶錢少了,即使他去銀行查詢日誌,他也只能發現確實有一個來自於他本人的合法請求轉移了資金,沒有任何被攻擊的痕跡。而 Mallory 則可以拿到錢後逍遙法外。
三 總結 用戶和網站通過正常登陸,建立了信任關係,在這種信任關係的有效期內,用戶通過廣告等形式進入了攻擊者的網站,攻擊者會在自己的網站內訪問與用戶建立的信任關係的網站的某些敏感操作,而此時,用戶和網站還在信任有效期內。 也可以通過類似xss的方式,上傳圖片之類的東西,src=某個請求,用戶進入這個網站之後會發送這條請求。 並不是只有get請求才能被csrf,攻擊者同樣可以通過表單完成一次post的csrf攻擊。
四 防禦
-
判斷referer:並不好,集團審覈未通過,已放棄這種較爲簡單的做法
-
jwt,比較常用,後端返回token,localstorage是有跨域限制的,其他域名無法訪問信任域的緩存,每次請求在請求上帶上token
---------------------------------------------------------------------------------------------------------------------------------
總體思路:前端進行token的session緩存,後臺攔截器進行session校驗
系統改造案例
1.添加攔截器代碼,主要作用是取jsp頁面存儲到session的token和request請求的token進行校驗;
public class CSRFInterceptor implements HandlerInterceptor {
static final StructLogger logger = StructLogger.getLogger(CSRFInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String[] requestToken = (String[])request.getAttribute("csrfToken");
// logger.info(requestToken[0]);
String sessionToken = (String) request.getSession().getAttribute("csrfToken");
// logger.info(sessionToken);
if (Objects.equals(requestToken[0],sessionToken) && StringUtils.isNotBlank(sessionToken)){
return true;
}
return false;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
添加攔截器xml
<!--配置攔截器, 多個攔截器,順序執行 -->
<mvc:interceptors>
<!--csrf漏洞攔截 -->
<mvc:interceptor>
<!-- 匹配的是url路徑, 如果不配置或/**,將攔截所有的Controller -->
<mvc:mapping path="/**"/>
<bean class="com.intime.hr.interceptor.CSRFInterceptor"></bean>
</mvc:interceptor>
<!-- 當設置多個攔截器時,先按順序調用preHandle方法,然後逆序調用每個攔截器的postHandle和afterCompletion方法 -->
<!--未授權攔截 -->
<mvc:interceptor>
<mvc:mapping path="/**"/>
<mvc:exclude-mapping path="/ehrlogin.htm"/>
<!--<mvc:exclude-mapping path="/ehrnologin.htm"/>-->
<bean class="com.intime.hr.interceptor.LoginStatusInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
由於request請求裏參數攜帶token,根據系統之前的做法會將token參數取出並且拼入sql上,導致報錯;所以需要配置一個過濾器進行過濾掉這個token參數;
public class CSRFFilter implements Filter {
public void destroy() {
}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
request = new MyHttpServletRequestWrapper((HttpServletRequest) request);
chain.doFilter(request, response);
}
public void init(FilterConfig config) throws ServletException {
}
class MyHttpServletRequestWrapper extends HttpServletRequestWrapper {
private HashMap<String, String[]> parameter;
public MyHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
parameter = new HashMap<String, String[]>();
Map<String, String[]> parameters = request.getParameterMap();
for(Map.Entry<String, String[]> entry : parameters.entrySet()) {
if(Objects.equals(entry.getKey(),"csrfToken")){
this.setAttribute("csrfToken",entry.getValue());
continue;
}
parameter.put(entry.getKey(), entry.getValue());
}
}
@Override
public String getParameter(String name) {
return parameter.get(name)==null?null:parameter.get(name)[0];
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public Map getParameterMap() {
return parameter;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
public Enumeration getParameterNames() {
return new Vector(parameter.keySet()).elements();
}
@Override
public String[] getParameterValues(String name) {
String[] result = null;
Object v = parameter.get(name);
if (v == null) {
result = null;
} else if (v instanceof String[]) {
result = (String[]) v;
} else if (v instanceof String) {
result = new String[] { (String) v };
} else {
result = new String[] { v.toString() };
}
return result;
}
}
}
過濾器web.xml配置文件
<filter>
<filter-name>csrfFilter</filter-name>
<filter-class>com.intime.hr.interceptor.CSRFFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>csrfFilter</filter-name>
<url-pattern>*.htm</url-pattern>
</filter-mapping>
前端jsp頁面改造
新增csrfToken.jsp頁面,主要作用向session裏存儲token,封裝所有頁面上的$.post和$.ajax請求添加token參數的傳遞;
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%
String csrfToken =session.getAttribute("csrfToken")==null?null:session.getAttribute("csrfToken").toString();
if(csrfToken==null) {
csrfToken = UUID.randomUUID().toString();
session.setAttribute("csrfToken",csrfToken);
}
%>
<script src="lib/1.js"></script>
<script src="https://code.jquery.com/jquery-1.8.3.min.js" language="javascript" type="text/javascript"></script>
<script language="javascript" type="text/javascript">
var csrfToken='<%=csrfToken%>';
$(function(){
var _ajax = $.ajax;
var _post = $.post;
$.ajax = function (options) {
options.data = $.extend(options.data, {csrfToken: csrfToken});
_ajax(options);
}
$.post = function () {
args = [];
for(var i= 0;i<arguments.length;i++){
args.push(arguments[i]);
}
var hasData = !(typeof args[1] == "function");
var data = !hasData ? {} : args[1];
data = $.extend(data, {csrfToken: csrfToken});
if(hasData){
args[1] = data;
}else{
var a1 = args.slice(0,1);
var a2 = args.slice(1,4);
a1.push(data);
args = a1.concat(a2);
}
_post(args[0],args[1],args[2],args[3]);
}
})
</script>
所有頁面引入csrfToken.jsp,例如
<html>
<head>
<base href="<%=basePath%>">
<%@ include file="/jsp/csrfToken.jsp" %>
<title>管理人員</title>
這個引入直接寫絕對路徑比較方便統一修改 :
<%@ include file="/jsp/csrfToken.jsp" %>
改完之後看看所有請求是否攜帶token參數
注意: 此jsp頁面必須引入html 標籤內(也就是有html標籤的頁面)