Java解決CSRF問題


項目地址: https://github.com/morethink/web-security

CSRF是什么?

CSRF(Cross-site request forgery),中文名稱:跨站請求偽造,也被稱為:one click attack/session riding,縮寫為:CSRF/XSRF。

CSRF可以做什么?

你這可以這么理解CSRF攻擊:攻擊者盜用了你的身份,以你的名義發送惡意請求。CSRF能夠做的事情包括:以你名義發送郵件,發消息,盜取你的賬號,甚至於購買商品,虛擬貨幣轉賬......造成的問題包括:個人隱私泄露以及財產安全。

CSRF的原理

下圖簡單闡述了CSRF攻擊的思

從上圖可以看出,要完成一次CSRF攻擊,受害者必須依次完成兩個步驟:

  1. 登錄受信任網站A,並在本地生成Cookie。
  2. 在不登出A的情況下,訪問危險網站B。

看到這里,你也許會說:“如果我不滿足以上兩個條件中的一個,我就不會受到CSRF的攻擊”。是的,確實如此,但你不能保證以下情況不會發生:

  1. 你不能保證你登錄了一個網站后,不再打開一個tab頁面並訪問另外的網站。
  2. 你不能保證你關閉瀏覽器了后,你本地的Cookie立刻過期,你上次的會話已經結束。(事實上,關閉瀏覽器不能結束一個會話,但大多數人都會錯誤的認為關閉瀏覽器就等於退出登錄/結束會話了......)
  3. 上圖中所謂的攻擊網站,可能是一個存在其他漏洞的可信任的經常被人訪問的網站。

下面講一講java解決CSRF攻擊的方式。

模擬CSRF攻擊

登錄A網站

用戶名和密碼都是admin。

http://localhost:8081/login.html:

你有權限刪除1號帖子

http://localhost:8081/deletePost.html:

登錄有CSRF攻擊A網站的B網站

http://localhost:8082/deletePost.html:

明顯看到B網站是8082端口,A網站是8081端口,但是B網站的刪除2號帖子功能依然實現。

預防CSRF攻擊

簡單來說,CSRF 就是網站 A 對用戶建立信任關系后,在網站 B 上利用這種信任關系,跨站點向網站 A 發起一些偽造的用戶操作請求,以達到攻擊的目的。

而之所以可以完成攻擊是因為B向A發起攻擊的時候會把A網站的cookie帶給A網站,也就是說cookie已經不安全了。

通過Synchronizer Tokens

Synchronizer Tokens: 在表單里隱藏一個隨機變化的 csrf_token csrf_token 提交到后台進行驗證,如果驗證通過則可以繼續執行操作。這種情況有效的主要原因是網站 B 拿不到網站 A 表單里的 csrf_token

這種方式的使用條件是PHP和JSP等。因為cookie已經不安全了,因此把csrf_token值存儲在session中,然后每次表單提交時都從session取出來放到form表單的隱藏域中,這樣B網站不可以得到這個存儲到session中的值。

下面是JSP的:

<input type="hidden" name="random_form" value=<%=random%>></input>

但是我現在的情況是html,不是JSP,並不能動態的從session中取出csrf_token值。只能采用加密的方式了。

Hash加密cookie中csrf_token值

這可能是最簡單的解決方案了,因為攻擊者不能獲得第三方的Cookie(理論上),所以表單中的數據也就構造失敗了。

我采用的hash加密方法是JS實現Java的HashCode方法,得到hash值,這個比較簡單。也可以采用其他的hash算法。

前端向后台傳遞hash之后的csrf_token值和cookie中的csrf_token值,后台拿到cookie中的csrf_token值后得到hashCode值然后與前端傳過來的值進行比較,一樣則通過。

你有權限刪除3號帖子

http://localhost:8081/deletePost.html

B網站的他已經沒有權限了

我們通過UserFilter.java給攻擊者返回的是403錯誤,表示服務器理解用戶客戶端的請求但拒絕處理。

http://localhost:8082/deletePost.html:

攻擊者不能刪除4號帖子。

前端代碼:

deletePost.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>deletePost</title>
    <script type="text/javascript" src="js/jquery.min.js"></script>
    <script type="text/javascript">
        function deletePost() {
            var url = '/post/' + document.getElementById("postId").value;
            var csrf_token = document.cookie.replace(/(?:(?:^|.*;\s*)csrf_token\s*\=\s*([^;]*).*$)|^.*$/, "$1");
            console.log('csrf_token=' + csrf_token);
            $.ajax({
                type: "post",//請求方式
                url: url,  //發送請求地址
                timeout: 30000,//超時時間:30秒
                data: {
                    "_method": "delete",
                    "csrf_token": hash(csrf_token) // 對csrf_token進行hash加密
                },
                dataType: "json",//設置返回數據的格式
                success: function (result) {
                    if (result.message == "success") {
//                    window.location.href = "index.html";
                        $("#result").text("刪除成功");
                    } else {
                        $("#result").text("刪除失敗");
                    }
                },
                error: function () { //請求出錯的處理
                    $("#result").text("請求出錯");
                }
            });
        }

        // javascript的String到int(32位)的hash算法
        function hash(str) {
            var hash = 0;
            if (str.length == 0) return hash;
            for (i = 0; i < str.length; i++) {
                char = str.charCodeAt(i);
                hash = ((hash << 5) - hash) + char;
                hash = hash & hash; // Convert to 32bit integer
            }
            return hash;
        }
    </script>
</head>
<body>
<h3>刪除帖子</h3>
帖子編號 : <input type="text" id="postId"/>
<button onclick="deletePost();">deletePost</button>

<br/>
<br/>
<br/>
<div>
    <p id="result"></p>
</div>
</body>
</html>

后台代碼:

UserInterceptor.java

package cn.morethink.interceptor;

import cn.morethink.util.JsonUtil;
import cn.morethink.util.Result;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;

/**
 * @author 李文浩
 * @date 2018/1/4
 */
public class UserInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String method = request.getMethod();
        System.out.println(method);
        if (method.equalsIgnoreCase("POST") || method.equalsIgnoreCase("DELETE")
                || method.equalsIgnoreCase("PUT")) {
            String csrf_token = request.getParameter("csrf_token");
            System.out.println(csrf_token + "1222222222222222222222222222222222222222222222");
            Cookie[] cookies = request.getCookies();
            if (cookies != null && cookies.length > 0 && csrf_token != null) {
                for (Cookie cookie : cookies) {
                    if (cookie.getName().equals("csrf_token")) {
                        if (Integer.valueOf(csrf_token) == cookie.getValue().hashCode()) {
                            return true;
                        }
                    }
                }
            }
        }
        Result result = new Result("403", "你還想攻擊我??????????", "");
        PrintWriter out = response.getWriter();
        out.write(JsonUtil.toJson(result));
        out.close();
        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 {

    }
}


注意

  1. cookie必須要設置PATH才可以生效,否則在下一次請求的時候無法帶給服務器。
  2. Spring Boot 出現啟動找不到主類的問題時可以mvn clean一下。
  3. Filter設置response.sendError(403)在Spring Boot沒有效果。

參考文檔

  1. 淺談CSRF攻擊方式
  2. jQueue 動態設置form表單的action屬性的值和方法
  3. javascript的String到int(32位)的hash算法


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM