CSRF漏洞的原理與防護

image

CSRF 全稱:Cross Site Request Forgery,譯:跨站請求僞造html

場景

點擊一個連接以後發現:帳號被盜,錢被轉走,或者莫名發表某些評論等一切本身不知情的操做。前端

CSRF是什麼

csrf 是一個能夠發送http請求的腳本。能夠假裝受害者向網站發送請求,達到修改網站數據的目的。chrome

原理

當你在瀏覽器上登陸某網站後,cookie會保存登陸的信息,這樣在繼續訪問的時候不用每次都登陸了,這個你們都知道。而CSRF就利用這個登錄態去發送惡意請求給後端。json

爲何腳本能夠得到目標網站的cookie呢?
只要是請求目標網站,瀏覽器會自動帶上該網站域名下面的cookie,看下面的腳本,能夠證實惡意腳本能夠得到CSDN網站的登陸信息。
前提是你已經在瀏覽器上登陸了CSND網站。後端

<!doctype html>
<html>
    <head>
        <meta charset="utf-8"/>
        <title>csrf demo</title>
    </head>
    <body>
    您在CSDN上的
        粉絲數:<span id="fans_num"></span>
        關注數:<span id="follow_num"></span>
        <script>
            fetch('https://me.csdn.net/api/relation/get', {
              credentials: 'include'  
            }).then(res => res.json())
            .then(
                res => {
                document.getElementById('fans_num').innerText = res.data.fans_num;
                document.getElementById('follow_num').innerText = res.data.follow_num;
            })  
        </script>
    </body>
</html>

保證CSDN的登陸狀態,用瀏覽器打開這個html文件,能夠看到這個腳本已經得到了我在csdn 上的用戶信息。以及寒酸的粉絲數量!
F12打開選擇應用程序一欄左邊Cookie 還有來自csdn網站關於當前用戶的一些信息。
image.png
image.png
image.pngapi

這個腳本讓每一個不一樣的登陸用戶打開,都會根據當前用戶來展現關注數和粉絲數,
這就足以說明能夠得到目標網站的當前用戶的信息,並可以表明用戶發送請求。
這只是個無害的get請求,若是是post請求呢?瀏覽器

CSRF攻擊

知道了原理,攻擊就變得好理解了,接着上面的例子,
我把請求地址改爲評論本篇文章的url,參數爲 「這篇文章寫得6」,
在沒有CSRF防護的狀況下,我發表一個評論如:脫單祕笈:,後面附上這個腳本的連接,只要有用戶點了連接,就會以他的名義給本篇文章發評論「這篇文章寫得6」。cookie

CSDN 確定是作了防護了哈,我就不白費力氣了。session

CSRF防護

三種防護方式:併發

1. SameSit

禁止第三方網站使用本站Cookie

這是後端在設置Cookie時候給SameSite的值設置爲Strict或者Lax
當設置Strict的時候表明第三方網站全部請求都不能使用本站的Cookie。
當設置Lax的時候表明只容許第三方網站的GET表單、<a>標籤和<link>標籤攜帶Cookie。
當設置None的時候表明和沒設同樣。

@Bean
public CookieSerializer httpSessionIdResolver(){
    DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
    cookieSerializer.setCookieName("JESSIONID");
    cookieSerializer.setUseHttpOnlyCookie(true);
    cookieSerializer.setSameSite("Lax");
    cookieSerializer.setUseSecureCookie(true);
    return cookieSerializer;
}

缺點:
目前只有chrome瀏覽器支持........

2. referer

referer表明着請求的來源,不能夠僞造。
後端寫個過濾器檢查請求的headers中的referer,檢驗是否是本網站的請求。
題外話:
refererorigin的區別,只有post請求會攜帶origin請求頭,而referer不論何種狀況下都帶。
referer正確的拼寫 應該是 referrer,HTTP的標準制定者們將錯就錯,不打算改了
缺點:
瀏覽器能夠關閉referer..........

3. token

最廣泛的一種防護方法,後端生成一個token放在session中併發給前端,前端發送請求時攜帶這個token,後端經過校驗這個token和session中的token是否一致判斷是不是本網站的請求。

具體實現:
用戶登陸輸入帳號密碼,請求登陸接口,後端在用戶登陸信息正確的狀況下將token放到session中,並返回token給前端,前端把token 存放在localstory中,以後再發送請求都會將token放到header中。
後端寫個過濾器,攔截POST請求,注意忽略掉不須要token的請求,好比登陸接口,獲取token的接口,省得尚未獲取token就檢驗token。
校驗原則: session中的token和前端header中的token一致的post ,放行。

/**
 * @author mashu
 * Date 2020/6/22 9:37
 */
@Slf4j
@Component
@WebFilter(urlPatterns = "/*", filterName = "verificationTokenFilter", description = "用於校驗token")
public class VerificationTokenFilter implements Filter {

    List<String> ignorePathList = ImmutableList.of("/demo/login","/demo/getToken");

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
        HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
        //忽略不須要token的請求
        String serviceUrl = httpServletRequest.getServletPath();
        for (final String ignorePath : ignorePathList) {
            if (serviceUrl.contains(ignorePath)) {
                filterChain.doFilter(servletRequest, servletResponse);
                return;
            }
        }
        String method = httpServletRequest.getMethod();
        if ("POST".equals(method)) {
           String tokenSession = (String)httpServletRequest.getSession().getAttribute("token");
           String token = httpServletRequest.getHeader("token");
            if (null != token && null != tokenSession && tokenSession.equals(token)) {
                filterChain.doFilter(servletRequest, servletResponse);
                return;
            } else {
                log.error("驗證token失敗!" + tokenSession + "!=" + token);
                httpServletResponse.sendError(403);
                return;
            }
        }
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {

    }
}
相關文章
相關標籤/搜索