1. 程式人生 > >CSRF spring mvc 跨站請求偽造防禦

CSRF spring mvc 跨站請求偽造防禦

CSRF
CSRF(Cross-site request forgery跨站請求偽造,也被稱為“One Click Attack”或者Session Riding,通常縮寫為CSRF或者XSRF,是一種對網站的惡意利用。儘管聽起來像跨站指令碼(XSS),但它與XSS非常不同,並且攻擊方式幾乎相左。XSS利用站點內的信任使用者,而CSRF則通過偽裝來自受信任使用者的請求來利用受信任的網站。與XSS攻擊相比,CSRF攻擊往往不大流行(因此對其進行防範的資源也相當稀少)和難以防範,所以被認為比XSS更具危險性。

攻擊示例
如:一個網站使用者Bob可能正在瀏覽聊天論壇,而同時另一個使用者Alice也在此論壇中,並且後者剛剛釋出了一個具有Bob銀行連結的圖片訊息。設想一下,Alice編寫了一個在Bob的銀行站點上進行取款的form提交的連結,並將此連結作為圖片src。如果Bob的銀行在cookie中儲存他的授權資訊,並且此cookie沒有過期,那麼當Bob的瀏覽器嘗試裝載圖片時將提交這個取款form和他的cookie,這樣在沒經Bob同意的情況下便授權了這次事務。
CSRF是一種依賴web瀏覽器的、被混淆過的代理人攻擊(deputy attack)。在上面銀行示例中的代理人是Bob的web瀏覽器,它被混淆後誤將Bob的授權直接交給了Alice使用。
下面是CSRF的常見特性:
依靠使用者標識危害網站
利用網站對使用者標識的信任
欺騙使用者的瀏覽器傳送HTTP請求給目標站點
另外可以通過IMG標籤會觸發一個GET請求,可以利用它來實現CSRF攻擊。

spring mvc 框架下防禦策略

思路概要:
1.初始化頁面時在token隱藏域。
2.表單提交後帶入token到後臺,驗證token,如成功繼續操否為受到攻擊。
3.操作完之後重新生成token到頁面隱藏域。
程式碼示例:
建立攔截器:

/**
 * <一句話功能簡述>
 * <功能詳細描述>
 * 防止跨站請求偽造攔截器
 * 為每個返回的頁面新增CSRFToken引數
 * @author  Tangguilin
 * @version  [版本號, 2016年3月26日]
 */
public class AvoidCSRFInterceptor
extends HandlerInterceptorAdapter {
@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String url = request.getRequestURI(); if (!url.endsWith(".do")) { return true; } HandlerMethod handlerMethod = (HandlerMethod)handler; Method method = handlerMethod.getMethod(); VerifyCSRFToken annotation = method.getAnnotation(VerifyCSRFToken.class); if
(annotation != null) { String xrq = request.getHeader("X-Requested-With");//是否為Ajax標誌 //非法的跨站請求校驗 if (annotation.verifyCSRFToken() && !verifyCSRFToken(request)) { if (StringUtil.isEmpty(xrq)) { //form表單提交,url get方式,重新整理csrftoken並跳轉提示頁面 request.getSession(false).setAttribute("CSRFToken", TokenProcessor.getInstance().generateToken(request)); response.setContentType("application/json;charset=UTF-8"); PrintWriter out = response.getWriter(); out.print("非法請求"); response.flushBuffer(); return false; } else { //重新整理CSRFToken,返回錯誤碼,用於ajax處理,可自定義 BaseDataResp baseResp = new BaseDataResp(); String csrftoken = TokenProcessor.getInstance().generateToken(request); request.getSession(false).setAttribute("CSRFToken", csrftoken); baseResp.setCode(IDiMengResultCode.SystemManager.CSRF); response.setContentType("application/json;charset=UTF-8"); PrintWriter out = response.getWriter(); out.print(baseResp.toString()); response.flushBuffer(); return false; } } } return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { String url = request.getRequestURI(); if (!url.endsWith(".do")) { return; } //第一次生成token if (modelAndView != null) { if (request.getSession(false) == null || StringUtil.isEmpty((String)request.getSession(false).getAttribute("CSRFToken"))) { request.getSession(false).setAttribute("CSRFToken", TokenProcessor.getInstance().generateToken(request)); return; } } //重新整理token HandlerMethod handlerMethod = (HandlerMethod)handler; Method method = handlerMethod.getMethod(); RefreshCSRFToken refreshAnnotation = method.getAnnotation(RefreshCSRFToken.class); String xrq = request.getHeader("X-Requested-With");//是否為Ajax標誌 if (refreshAnnotation != null && refreshAnnotation.refreshCSRFToken() && StringUtil.isEmpty(xrq)) { request.getSession(false).setAttribute("CSRFToken", TokenProcessor.getInstance().generateToken(request)); return; } VerifyCSRFToken verifyAnnotation = method.getAnnotation(VerifyCSRFToken.class); if (verifyAnnotation != null) { //成功後重新整理token if (verifyAnnotation.verifyCSRFToken()) { if (StringUtil.isEmpty(xrq)) { request.getSession(false).setAttribute("CSRFToken", TokenProcessor.getInstance().generateToken(request)); } else { Map<String, String> map = new HashMap<String, String>(); map.put("CSRFToken", TokenProcessor.getInstance().generateToken(request)); response.setContentType("application/json;charset=UTF-8"); OutputStream out = response.getOutputStream(); out.write((",'csrf':" + JSONObject.toJSONString(map) + "}").getBytes("UTF-8")); } } } } /** <一句話功能簡述> * 處理跨站請求偽造 * 針對需要登入後才能處理的請求,驗證CSRFToken校驗 * @author tangguilin * @param request */ protected boolean verifyCSRFToken(HttpServletRequest request) { String requstCSRFToken = request.getHeader("CSRFToken");//請求中的CSRFToken if (StringUtil.isEmpty(requstCSRFToken)) { return false; } String sessionCSRFToken = (String)request.getSession().getAttribute("CSRFToken"); if (StringUtil.isEmpty(sessionCSRFToken)) { return false; } return requstCSRFToken.equals(sessionCSRFToken); } }

註解類:

/**
 * 跨站請求仿照註解
 * 重新整理CSRFToken
 * @author  Tangguilin
 * @version  [版本號, 2016年3月28日]
 */
@Target({java.lang.annotation.ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RefreshCSRFToken
{
    /** 
     * 重新整理CSRFToken
     * @author tangguilin
     * @return
     */
    public abstract boolean refreshCSRFToken();
}

/**
 * 跨站請求仿照註解
 * 
 * @author  Tangguilin
 * @version  [版本號, 2016年3月28日]
 */
@Target({java.lang.annotation.ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface VerifyCSRFToken
{
    /** 
     * 需要驗證防跨站請求
     * @author tangguilin
     * @return
     */
    public abstract boolean verifyCSRFToken();

}

配置攔截:

   <mvc:interceptors>   
        <mvc:interceptor>  
            <mvc:mapping path="/**"/> 
            <!-- 定義在mvc:interceptor下面的表示是對特定的請求才進行攔截的 -->  
           <bean class="com.front.interceptor.AvoidCSRFInterceptor"/>  
        </mvc:interceptor>  
    </mvc:interceptors> 

在jsp頁面中新增隱藏域

 <input type="hidden" name="CSRFToken" value="${CSRFToken }"></input>  

修改公用Ajax

 ajax: function(options) {
            var datas = null;
            if (options["isAjaxForm"]) {
                $("#" + options["formId"]).submit();
            } else {
                if (options["serialize"]) {
                    if (options["formId"]) {
                        datas = $("#" + options["formId"]).serialize();
                    } else {
                        datas = $(document.forms[0]).serialize();
                    }
                }
                var defaultOptions = {
                    type: "post",
                    async: false,
                    data: datas
                };
                options = $.extend(defaultOptions, options);
                var path="";
                if(options["url"].indexOf(basePath)==-1){
                    path=basePath;
                }
                var headers = {};
                headers['CSRFToken'] = $("input[name='CSRFToken']").val();
                $.ajax({
                    type: options["type"],
                    headers: headers,
                    async: options["async"],
                    dataType: options["dataType"],
                    url: path + options["url"],
                    data: options["data"],
                    success: function(data) {
                        //登入超時重新整理頁面後跳轉首頁
                        if("2000062"==data.code){
                            window.location.reload();
                        }
                        //處理跨站請求偽造
                        if("666666" == data.code){
                            if(Dialog){
                                Dialog.show("操作失敗,請重新整理頁面重試","error");
                            }else{
                                alert("操作失敗,請重新整理頁面重試");
                            }
                            return false;
                        }
                        if (typeof(options["success"]) == "function") {
                            options["success"](data);
                        }
                    },
                    error: function(data) {
                        //CSRFToken處理
                        if(data && data.readyState && data.readyState == '4'){
                            var responseBody = data.responseText;
                            if(responseBody){
                                responseBody = "{'retData':"+responseBody;
                                var resJson = eval('(' + responseBody + ')');
                                $("input[name='CSRFToken']").val(resJson.csrf.CSRFToken);
                                if (typeof(options["success"]) == "function") {
                                    options["success"](resJson.retData);
                                }
                            }
                            return ;
                        }
                        //登入超時跳轉登入頁
                        /*if(data.responseText){
                             window.location.reload();//重新整理當前頁面.
                            return;
                        }*/
                        if (typeof(options["error"]) == "function") {
                            options["error"](data);
                        }else{
                            alert("請求地址:"+options["url"]+"  出現異常!請聯絡管理員!");
                        }

                    }
                });
            }
        }

使用場景
為需要防禦的Controller加上@VerifyCSRFToken(verifyCSRFToken = true)註解

@RefreshCSRFToken(refreshCSRFToken=true):如新開啟的頁面,彈出框體需要CSRFToken時使用