1. 程式人生 > >CSRF 詳解:攻擊,防禦,Spring Security應用等

CSRF 詳解:攻擊,防禦,Spring Security應用等

本文原創,更多內容可以參考: Java 全棧知識體系。如需轉載請說明原處。

CSRF(Cross-site request forgery跨站請求偽造,也被稱成為“one click attack”或者session riding,通常縮寫為CSRF或者XSRF,是一種對網站的惡意利用。 @pdai

CSRF 簡介

CSRF(Cross Site Request Forgery, 跨站域請求偽造)是一種網路的攻擊方式,它在 2007 年曾被列為網際網路 20 大安全隱患之一。其他安全隱患,比如 SQL 指令碼注入,跨站域指令碼攻擊等在近年來已經逐漸為眾人熟知,很多網站也都針對他們進行了防禦。然而,對於大多數人來說,CSRF 卻依然是一個陌生的概念。即便是大名鼎鼎的 Gmail, 在 2007 年底也存在著 CSRF 漏洞,從而被黑客攻擊而使 Gmail 的使用者造成巨大的損失。

CSRF 如何攻擊

先看圖:

從上圖可以看出,A網站通過cookie來識別使用者(C),當用戶成功進行身份驗證之後瀏覽器就會得到一個標識其身份的cookie,只要不關閉瀏覽器或者退出登入,以後訪問A網站會一直帶上這個cookie。如果這期間瀏覽器被人控制著向A網站發起請求去執行一些使用者不想做的功能(比如新增賬號),這就是會話劫持了。因為這個不是使用者真正想發出的請求,這就是所謂的“請求偽造”。此外,由於請求可以從第三方網站提交,所以字首跨站二字,即從B網站發起。

具體到銀行轉賬為例(這是網上的一個例子,一大坨...):

CSRF 攻擊可以在受害者毫不知情的情況下以受害者名義偽造請求傳送給受攻擊站點,從而在並未授權的情況下執行在許可權保護之下的操作。比如說,受害者 Bob 在銀行有一筆存款,通過對銀行的網站傳送請求 http://bank.example/withdraw?account=bob&amount=1000000&for=bob2

可以使 Bob 把 1000000 的存款轉到 bob2 的賬號下。通常情況下,該請求傳送到網站後,伺服器會先驗證該請求是否來自一個合法的 session,並且該 session 的使用者 Bob 已經成功登陸。黑客 Mallory 自己在該銀行也有賬戶,他知道上文中的 URL 可以把錢進行轉帳操作。Mallory 可以自己傳送一個請求給銀行:http://bank.example/withdraw?account=bob&amount=1000000&for=Mallory。但是這個請求來自 Mallory 而非 Bob,他不能通過安全認證,因此該請求不會起作用。這時,Mallory 想到使用 CSRF 的攻擊方式,他先自己做一個網站,在網站中放入如下程式碼: src=”http://bank.example/withdraw?account=bob&amount=1000000&for=Mallory ”
,並且通過廣告等誘使 Bob 來訪問他的網站。當 Bob 訪問該網站時,上述 url 就會從 Bob 的瀏覽器發向銀行,而這個請求會附帶 Bob 瀏覽器中的 cookie 一起發向銀行伺服器。大多數情況下,該請求會失敗,因為他要求 Bob 的認證資訊。但是,如果 Bob 當時恰巧剛訪問他的銀行後不久,他的瀏覽器與銀行網站之間的 session 尚未過期,瀏覽器的 cookie 之中含有 Bob 的認證資訊。這時,悲劇發生了,這個 url 請求就會得到響應,錢將從 Bob 的賬號轉移到 Mallory 的賬號,而 Bob 當時毫不知情。等以後 Bob 發現賬戶錢少了,即使他去銀行查詢日誌,他也只能發現確實有一個來自於他本人的合法請求轉移了資金,沒有任何被攻擊的痕跡。而 Mallory 則可以拿到錢後逍遙法外。

CSRF 理解的注意點

要理解CSRF,我認為你需要理解如下幾個問題:@pdai

黑客能拿到Cookie嗎?

CSRF 攻擊是黑客藉助受害者的 cookie 騙取伺服器的信任,但是黑客並不能拿到 cookie,也看不到 cookie 的內容。

對於伺服器返回的結果,由於瀏覽器同源策略的限制,黑客也無法進行解析。因此,黑客無法從返回的結果中得到任何東西,他所能做的就是給伺服器傳送請求,以執行請求中所描述的命令,在伺服器端直接改變資料的值,而非竊取伺服器中的資料。

什麼樣的請求是要CSRF保護的?

為什麼有些框架(比如Spring Security)裡防護CSRF的filter限定的Method是POST/PUT/DELETE等,而沒有限定GET Method?

我們要保護的物件是那些可以直接產生資料改變的服務,而對於讀取資料的服務,則不需要進行 CSRF 的保護。通常而言GET請作為請求資料,不作為修改資料,所以這些框架沒有攔截Get等方式請求。比如銀行系統中轉賬的請求會直接改變賬戶的金額,會遭到 CSRF 攻擊,需要保護。而查詢餘額是對金額的讀取操作,不會改變資料,CSRF 攻擊無法解析伺服器返回的結果,無需保護。

為什麼對請求做了CSRF攔截,但還是會報CRSF漏洞?

為什麼我在前端已經採用POST+CSRF Token請求,後端也對POST請求做了CSRF Filter,但是滲透測試中還有CSRF漏洞?

直接看下面程式碼。

// 這裡沒有限制POST Method,導致使用者可以不通過POST請求提交資料。
@RequestMapping("/url")
public ReponseData saveSomething(XXParam param){
    // 資料儲存操作...
}

PS:這一點是很容易被忽視的,在筆者經歷過的幾個專案的滲透測試中,多次出現。@pdai

CSRF 防禦常規思路

一定要注意,下面只是給你提供常規思路而已(以下文字摘自CSRF 攻擊的應對之道,具體實現請看下一個章節。@pdai

驗證 HTTP Referer 欄位

根據 HTTP 協議,在 HTTP 頭中有一個欄位叫 Referer,它記錄了該 HTTP 請求的來源地址。在通常情況下,訪問一個安全受限頁面的請求來自於同一個網站,比如需要訪問 http://bank.example/withdraw?account=bob&amount=1000000&for=Mallory,使用者必須先登陸 bank.example,然後通過點選頁面上的按鈕來觸發轉賬事件。這時,該轉帳請求的 Referer 值就會是轉賬按鈕所在的頁面的 URL,通常是以 bank.example 域名開頭的地址。而如果黑客要對銀行網站實施 CSRF 攻擊,他只能在他自己的網站構造請求,當用戶通過黑客的網站傳送請求到銀行時,該請求的 Referer 是指向黑客自己的網站。因此,要防禦 CSRF 攻擊,銀行網站只需要對於每一個轉賬請求驗證其 Referer 值,如果是以 bank.example 開頭的域名,則說明該請求是來自銀行網站自己的請求,是合法的。如果 Referer 是其他網站的話,則有可能是黑客的 CSRF 攻擊,拒絕該請求。

在請求地址中新增 token 並驗證

CSRF 攻擊之所以能夠成功,是因為黑客可以完全偽造使用者的請求,該請求中所有的使用者驗證資訊都是存在於 cookie 中,因此黑客可以在不知道這些驗證資訊的情況下直接利用使用者自己的 cookie 來通過安全驗證。要抵禦 CSRF,關鍵在於在請求中放入黑客所不能偽造的資訊,並且該資訊不存在於 cookie 之中。可以在 HTTP 請求中以引數的形式加入一個隨機產生的 token,並在伺服器端建立一個攔截器來驗證這個 token,如果請求中沒有 token 或者 token 內容不正確,則認為可能是 CSRF 攻擊而拒絕該請求。

這種方法要比檢查 Referer 要安全一些,token 可以在使用者登陸後產生並放於 session 之中,然後在每次請求時把 token 從 session 中拿出,與請求中的 token 進行比對,但這種方法的難點在於如何把 token 以引數的形式加入請求。對於 GET 請求,token 將附在請求地址之後,這樣 URL 就變成 http://url?csrftoken=tokenvalue。 而對於 POST 請求來說,要在 form 的最後加上<input type=”hidden” name=”csrftoken” value=”tokenvalue”/>,這樣就把 token 以引數的形式加入請求了。但是,在一個網站中,可以接受請求的地方非常多,要對於每一個請求都加上 token 是很麻煩的,並且很容易漏掉,通常使用的方法就是在每次頁面載入時,使用 javascript 遍歷整個 dom 樹,對於 dom 中所有的 a 和 form 標籤後加入 token。這樣可以解決大部分的請求,但是對於在頁面載入之後動態生成的 html 程式碼,這種方法就沒有作用,還需要程式設計師在編碼時手動新增 token。

該方法還有一個缺點是難以保證 token 本身的安全。特別是在一些論壇之類支援使用者自己發表內容的網站,黑客可以在上面釋出自己個人網站的地址。由於系統也會在這個地址後面加上 token,黑客可以在自己的網站上得到這個 token,並馬上就可以發動 CSRF 攻擊。為了避免這一點,系統可以在新增 token 的時候增加一個判斷,如果這個連結是鏈到自己本站的,就在後面新增 token,如果是通向外網則不加。不過,即使這個 csrftoken 不以引數的形式附加在請求之中,黑客的網站也同樣可以通過 Referer 來得到這個 token 值以發動 CSRF 攻擊。這也是一些使用者喜歡手動關閉瀏覽器 Referer 功能的原因。

在 HTTP 頭中自定義屬性並驗證

這種方法也是使用 token 並進行驗證,和上一種方法不同的是,這裡並不是把 token 以引數的形式置於 HTTP 請求之中,而是把它放到 HTTP 頭中自定義的屬性裡。通過 XMLHttpRequest 這個類,可以一次性給所有該類請求加上 csrftoken 這個 HTTP 頭屬性,並把 token 值放入其中。這樣解決了上種方法在請求中加入 token 的不便,同時,通過 XMLHttpRequest 請求的地址不會被記錄到瀏覽器的位址列,也不用擔心 token 會透過 Referer 洩露到其他網站中去。

然而這種方法的侷限性非常大。XMLHttpRequest 請求通常用於 Ajax 方法中對於頁面區域性的非同步重新整理,並非所有的請求都適合用這個類來發起,而且通過該類請求得到的頁面不能被瀏覽器所記錄下,從而進行前進,後退,重新整理,收藏等操作,給使用者帶來不便。另外,對於沒有進行 CSRF 防護的遺留系統來說,要採用這種方法來進行防護,要把所有請求都改為 XMLHttpRequest 請求,這樣幾乎是要重寫整個網站,這代價無疑是不能接受的。

CSRF 防禦實戰

主流的框架一般都包含了CSRF的攔截。

非框架型 - 自定義XXXCsrfFilter

可以通過自定義xxxCsrfFilter去攔截實現, 這裡建議你參考 Spring Security - org.springframework.security.web.csrf.CsrfFilter.java。

Spring Security - 什麼時候禁用CSRF

你開發的應用在何時,會考慮禁用CSRF呢? 這時候需要考慮CSRF本質是盜用cookie, 無cookie方案就可以禁用。

  • 如果你只是建立一個非瀏覽器客戶端使用的服務,你可能會想要禁用CSRF保護

Spring Security中禁用CSRF

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
 
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();// 預設是啟用的,需要禁用CSRF保護
    }
}

Spring Security - CookieCsrfTokenRepository.withHttpOnlyFalse()

存Cookie,比如前後端分離方案:Spring Security CookieCsrfTokenRepository + 前端路由統一設定

Spring Security依賴包

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

Spring Security - CookieCsrfTokenRepository.withHttpOnlyFalse()

@Override
protected void configure(HttpSecurity http) throws Exception {
    // 本例子給個範例而已,對於xxx的部分,自己根據業務定義
    http
        .authorizeRequests()
            /* allow */
            .antMatchers("/plugins/**", "/api-docs/**") .permitAll()
            .antMatchers("/login", "/logout").permitAll()
            
            /* auth control */
            .antMatchers("/xxx/user", "/xxx/user/**").access("hasAuthority('xxx:user')")
            .antMatchers("/xxx/role", "/xxx/role/**").access("hasAuthority('xxx:role')")

            /* others */
            .anyRequest().authenticated()
           
        /* other Filters */
        .and()
            .addFilterBefore(xxxFilter(), UsernamePasswordAuthenticationFilter.class)
        
        /* iframe */
        .headers()
            .frameOptions()
            .sameOrigin()
        
        /* form login & logout */
        .and().formLogin()
            .loginPage("/login")
            .usernameParameter("username")
            .passwordParameter("password")
            .defaultSuccessUrl("/admin/", true)
        .and().rememberMe()
            .rememberMeParameter("remember")
            .rememberMeCookieName("remember")
        .and().logout()
            .deleteCookies("JSESSIONID")
            .invalidateHttpSession(true)
            .logoutSuccessHandler(new XXXLogoutSuccessHandler(localeResolver()))
            .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
            .permitAll()
        
        /* csrf */
        .and().csrf()
            .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
//      .and().cors()
    
}

後端thymeleaf登入頁面"/login":

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>登入頁面</title>
</head>
<body>
<form id="form" method="post">
    <label>使用者名稱:</label><input name="username" type="text" value="" />
    <label>密碼:</label><input name="password" type="text" value="" />
    <!--csrf驗證需要-->
    <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
    <br/>
    <input type="submit" value="登入">
</form>
</body>
</html>

前端呼叫後端API: 方式一 (前後端分離的):

//  將Cookie轉換為JS Object
function initCookies() {
    var cookie = document.cookie,
        items = cookie.split(";"),
        keys = {};
    items.forEach(function(item) {
        var kv = item.split('=');
        keys[$.trim(kv[0])] = $.trim(kv[1]);
    });
    return keys;
}
//  提交資料
$.post(url, {
    userId : code,
    _csrf : initCookies()['X-XSRF-TOKEN'];
}, function(datas) {
    //  TODO something
})

前端呼叫後端API: 方式二 (後端寫前端,用的後端模板) :

<meta name="_csrf" content="${_csrf.token}"/>
<meta name="_csrf_header" content="${_csrf.headerName}"/>
 
<script>
 
    var token = $("meta[name='_csrf']").attr("content");
    var header = $("meta[name='_csrf_header']").attr("content");
    $.ajaxSetup({
        beforeSend: function (xhr) {
            if(header && token ){
                xhr.setRequestHeader(header, token);
            }
        }}
    );
</script>

Spring Security - new CookieCsrfTokenRepository()

可以通過new CookieCsrfTokenRepository()自定義攔截的邏輯,大概意思:

@Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().csrfTokenRepository(new CookieCsrfTokenRepository())
                .requireCsrfProtectionMatcher(
                        /**
                         * 攔截“/login”開頭的訪問路徑,不讓訪問
                         * 攔截所有“POST”請求,不讓訪問
                         */
//                        httpServletRequest -> httpServletRequest.getRequestURI().startsWith("/login")
//                                && httpServletRequest.getMethod().equals("POST")
                        httpServletRequest -> httpServletRequest.getMethod().equals("POST")
                );
    }
}

當然也可以這麼寫,可以看後續對預設的DefaultRequiresCsrfMatcher的原始碼

public class CsrfSecurityRequestMatcher implements RequestMatcher {
    private Pattern allowedMethods = Pattern.compile("^(GET|HEAD|TRACE|OPTIONS)$");
    private RegexRequestMatcher unprotectedMatcher = new RegexRequestMatcher("^/rest/.*", null);
 
    @Override
    public boolean matches(HttpServletRequest request) {
        if(allowedMethods.matcher(request.getMethod()).matches()){
            return false;
        }
 
        return !unprotectedMatcher.matches(request);
    }
}

Spring Security - CookieCsrfTokenRepository如何工作的呢?

CookieCsrfTokenRepository.withHttpOnlyFalse() 本質就是new CookieCsrfTokenRepository()

public static CookieCsrfTokenRepository withHttpOnlyFalse() {
    CookieCsrfTokenRepository result = new CookieCsrfTokenRepository();
    result.setCookieHttpOnly(false);
    return result;
}

為何預設的存放CSRFToken的cookie是httpOnly呢?

如果cookie中設定了HttpOnly屬性,那麼通過js指令碼將無法讀取到cookie資訊,這樣能有效的防止XSS攻擊,竊取cookie內容,這樣就增加了cookie的安全性,即便是這樣,也不要將重要資訊存入cookie。XSS全稱Cross SiteScript,跨站指令碼攻擊,是Web程式中常見的漏洞,XSS屬於被動式且用於客戶端的攻擊方式,所以容易被忽略其危害性。其原理是攻擊者向有XSS漏洞的網站中輸入(傳入)惡意的HTML程式碼,當其它使用者瀏覽該網站時,這段HTML程式碼會自動執行,從而達到攻擊的目的。如,盜取使用者Cookie、破壞頁面結構、重定向到其它網站等。

// 比如,設定https的cookie
response.addHeader("Set-Cookie", "uid=112; Path=/; Secure; HttpOnly");

Cookie CsrfToken 預設的封裝

static final String DEFAULT_CSRF_COOKIE_NAME = "XSRF-TOKEN";

static final String DEFAULT_CSRF_PARAMETER_NAME = "_csrf";

static final String DEFAULT_CSRF_HEADER_NAME = "X-XSRF-TOKEN";

private String parameterName = DEFAULT_CSRF_PARAMETER_NAME;

private String headerName = DEFAULT_CSRF_HEADER_NAME;

private String cookieName = DEFAULT_CSRF_COOKIE_NAME;

@Override
public CsrfToken generateToken(HttpServletRequest request) {
    return new DefaultCsrfToken(this.headerName, this.parameterName,
            createNewToken());
}

CsrfToken的儲存

@Override
public void saveToken(CsrfToken token, HttpServletRequest request,
        HttpServletResponse response) {
    String tokenValue = token == null ? "" : token.getToken();
    Cookie cookie = new Cookie(this.cookieName, tokenValue);
    cookie.setSecure(request.isSecure());
    if (this.cookiePath != null && !this.cookiePath.isEmpty()) {
            cookie.setPath(this.cookiePath);
    } else {
            cookie.setPath(this.getRequestContext(request));
    }
    if (token == null) {
        cookie.setMaxAge(0);
    }
    else {
        cookie.setMaxAge(-1);
    }
    if (cookieHttpOnly && setHttpOnlyMethod != null) {
        ReflectionUtils.invokeMethod(setHttpOnlyMethod, cookie, Boolean.TRUE);
    }

    response.addCookie(cookie);
}

CsrfToken的載入

@Override
public CsrfToken loadToken(HttpServletRequest request) {
    Cookie cookie = WebUtils.getCookie(request, this.cookieName);
    if (cookie == null) {
        return null;
    }
    String token = cookie.getValue();
    if (!StringUtils.hasLength(token)) {
        return null;
    }
    return new DefaultCsrfToken(this.headerName, this.parameterName, token);
}

Spring Security - CsrfFilter是如何完成攔截和校驗的呢?

public final class CsrfFilter extends OncePerRequestFilter {
    // 負責CsrfToken生成,載入等
    private final CsrfTokenRepository tokenRepository;
    
    // 負責攔截Csrf的匹配
    private RequestMatcher requireCsrfProtectionMatcher = DEFAULT_CSRF_MATCHER;
    
    // 被攔截後的拒絕策略
    private AccessDeniedHandler accessDeniedHandler = new AccessDeniedHandlerImpl();

    // CsrfFilter的過濾邏輯
    @Override
    protected void doFilterInternal(HttpServletRequest request,
            HttpServletResponse response, FilterChain filterChain)
                    throws ServletException, IOException {
        request.setAttribute(HttpServletResponse.class.getName(), response);

        // 載入token,沒有的自動生成一個
        CsrfToken csrfToken = this.tokenRepository.loadToken(request);
        final boolean missingToken = csrfToken == null;
        if (missingToken) {
            csrfToken = this.tokenRepository.generateToken(request);
            this.tokenRepository.saveToken(csrfToken, request, response);
        }
        request.setAttribute(CsrfToken.class.getName(), csrfToken);
        request.setAttribute(csrfToken.getParameterName(), csrfToken);

        // 攔截請求
        if (!this.requireCsrfProtectionMatcher.matches(request)) {
            filterChain.doFilter(request, response);
            return;
        }

        // 校驗token
        String actualToken = request.getHeader(csrfToken.getHeaderName());
        if (actualToken == null) {
            actualToken = request.getParameter(csrfToken.getParameterName());
        }
        if (!csrfToken.getToken().equals(actualToken)) {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Invalid CSRF token found for "
                        + UrlUtils.buildFullRequestUrl(request));
            }
            if (missingToken) {
                this.accessDeniedHandler.handle(request, response,
                        new MissingCsrfTokenException(actualToken));
            }
            else {
                this.accessDeniedHandler.handle(request, response,
                        new InvalidCsrfTokenException(csrfToken, actualToken));
            }
            return;
        }

        filterChain.doFilter(request, response);
    }
}

Spring Security - 預設對哪些Method攔截呢?

"GET", "HEAD", "TRACE", "OPTIONS" 不會攔截:

private static final class DefaultRequiresCsrfMatcher implements RequestMatcher {
    private final HashSet<String> allowedMethods = new HashSet<String>(
            Arrays.asList("GET", "HEAD", "TRACE", "OPTIONS"));

    /*
        * (non-Javadoc)
        *
        * @see
        * org.springframework.security.web.util.matcher.RequestMatcher#matches(javax.
        * servlet.http.HttpServletRequest)
        */
    @Override
    public boolean matches(HttpServletRequest request) {
        return !this.allowedMethods.contains(request.getMethod());
    }
}

Spring Security - HttpSessionCsrfTokenRepository

經過上面的分析,你再看Session的,是不是很簡單? 我這邊貼個程式碼,你眼睛掃一下即可。@pdai

public final class HttpSessionCsrfTokenRepository implements CsrfTokenRepository {
    private static final String DEFAULT_CSRF_PARAMETER_NAME = "_csrf";

    private static final String DEFAULT_CSRF_HEADER_NAME = "X-CSRF-TOKEN";

    private static final String DEFAULT_CSRF_TOKEN_ATTR_NAME = HttpSessionCsrfTokenRepository.class
            .getName().concat(".CSRF_TOKEN");

    private String parameterName = DEFAULT_CSRF_PARAMETER_NAME;

    private String headerName = DEFAULT_CSRF_HEADER_NAME;

    private String sessionAttributeName = DEFAULT_CSRF_TOKEN_ATTR_NAME;

    /*
     * (non-Javadoc)
     *
     * @see org.springframework.security.web.csrf.CsrfTokenRepository#saveToken(org.
     * springframework .security.web.csrf.CsrfToken,
     * javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
     */
    public void saveToken(CsrfToken token, HttpServletRequest request,
            HttpServletResponse response) {
        if (token == null) {
            HttpSession session = request.getSession(false);
            if (session != null) {
                session.removeAttribute(this.sessionAttributeName);
            }
        }
        else {
            HttpSession session = request.getSession();
            session.setAttribute(this.sessionAttributeName, token);
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * org.springframework.security.web.csrf.CsrfTokenRepository#loadToken(javax.servlet
     * .http.HttpServletRequest)
     */
    public CsrfToken loadToken(HttpServletRequest request) {
        HttpSession session = request.getSession(false);
        if (session == null) {
            return null;
        }
        return (CsrfToken) session.getAttribute(this.sessionAttributeName);
    }

    /*
     * (non-Javadoc)
     *
     * @see org.springframework.security.web.csrf.CsrfTokenRepository#generateToken(javax.
     * servlet .http.HttpServletRequest)
     */
    public CsrfToken generateToken(HttpServletRequest request) {
        return new DefaultCsrfToken(this.headerName, this.parameterName,
                createNewToken());
    }
}

Spring Security - 設定Csrf不對會造成哪些錯誤呢?

  • 403 - 用CSRF作為控制權限,引發許可權問題
There was an unexpected error (type=Forbidden, status=403).
Invalid CSRF Token 'null' was found on the request parameter '_csrf' or header 'X-XSRF-TOKEN'.
  • 405 - 前置的引數繫結問題
POST method not supported。// 本質上還是引數繫結時,Csrf沒有設定或者不正確。

總結與展望

可見,CSRF 是一種危害非常大的攻擊,又很難以防範。目前幾種防禦策略雖然可以很大程度上抵禦 CSRF 的攻擊,但並沒有一種完美的解決方案。一些新的方案正在研究之中,比如對於每次請求都使用不同的動態口令,把 Referer 和 token 方案結合起來,甚至嘗試修改 HTTP 規範,但是這些新的方案尚不成熟,要正式投入使用並被業界廣為接受還需時日。在這之前,我們只有充分重視 CSRF,根據系統的實際情況選擇最合適的策略,這樣才能把 CSRF 的危害降到最低。

參考文章

  • https://www.ibm.com/developerworks/cn/web/1102_niugang_csrf/index.html
  • https://blog.csdn.net/panchang199266/article/details/83152587
  • https://www.freebuf.com/column/186939.html
  • https://blog.csdn.net/u013185616/article/details/70446392
  • https://blog.csdn.net/yiifaa/article/details/78459677

更多內容

最全的Java後端知識體系 https://www.pdai.tech, 每天更新中...。