防止 ASP.NET MVC 應用程式中的跨網站偽造要求 (CSRF) 攻擊

跨網站偽造要求 (CSRF) 是攻擊,惡意網站會將要求傳送至使用者目前登入的易受攻擊網站

以下是 CSRF 攻擊的範例:

  1. 使用者使用表單驗證登入 www.example.com

  2. 伺服器會驗證使用者。 來自伺服器的回應包含驗證 Cookie。

  3. 若未登出,使用者就會流覽惡意網站。 此惡意網站包含下列 HTML 格式:

    <h1>You Are a Winner!</h1>
      <form action="http://example.com/api/account" method="post">
        <input type="hidden" name="Transaction" value="withdraw" />
        <input type="hidden" name="Amount" value="1000000" />
      <input type="submit" value="Click Me"/>
    </form>
    

    請注意,表單動作張貼到易受攻擊的網站,而不是惡意網站。 這是 CSRF 的「跨網站」部分。

  4. 使用者按一下 [提交] 按鈕。 瀏覽器包含具有要求的驗證 Cookie。

  5. 要求會以使用者的驗證內容在伺服器上執行,而且可以執行已驗證使用者允許執行的任何動作。

雖然此範例需要使用者按一下表單按鈕,但惡意頁面可能就像輕鬆地執行自動提交表單的腳本一樣。 此外,使用 SSL 不會防止 CSRF 攻擊,因為惡意網站可以傳送「HTTPs://」要求。

一般而言,CSRF 攻擊可能會針對使用 Cookie 進行驗證的網站,因為瀏覽器會將所有相關 Cookie 傳送至目的地網站。 不過,CSRF 攻擊不限於惡意探索 Cookie。 例如,基本和摘要式驗證也很容易遭受攻擊。 使用者以基本或摘要式驗證登入之後。 瀏覽器會自動傳送認證,直到會話結束為止。

防偽造權杖

為了協助防止 CSRF 攻擊,ASP.NET MVC 使用反偽造權杖,也稱為 要求驗證權杖

  1. 用戶端會要求包含表單的 HTML 頁面。
  2. 伺服器在回應中包含兩個權杖。 一個權杖會以 Cookie 的形式傳送。 另一個會放在隱藏的表單欄位。 系統會隨機產生權杖,讓敵人無法猜測這些值。
  3. 當用戶端提交表單時,必須將這兩個權杖傳回伺服器。 用戶端會將 Cookie 權杖當作 Cookie 傳送,並在表單資料內傳送表單權杖。 (當使用者提交 form.) 時,瀏覽器用戶端會自動執行此動作
  4. 如果要求不包含這兩個權杖,則伺服器不允許要求。

以下是具有隱藏表單標記的 HTML 表單範例:

<form action="/Home/Test" method="post">
    <input name="__RequestVerificationToken" type="hidden"   
           value="6fGBtLZmVBZ59oUad1Fr33BuPxANKY9q3Srr5y[...]" />    
    <input type="submit" value="Submit" />
</form>

防偽造權杖的運作方式是惡意頁面無法讀取使用者的權杖,因為相同的原始來源原則。 (相同原始來源原則 可防止裝載于兩個不同網站上的檔存取彼此的內容。因此,在先前的範例中,惡意頁面可以將要求傳送至 example.com,但無法讀取 response.)

若要防止 CSRF 攻擊,請使用反偽造權杖搭配任何驗證通訊協定,其中瀏覽器會在使用者登入之後以無訊息方式傳送認證。 這包括 Cookie 型驗證通訊協定,例如表單驗證,以及基本驗證和摘要式驗證等通訊協定。

您應該針對 POST、PUT、DELETE) (任何非安全方法要求防偽權杖。 此外,請確定安全的方法 (GET、HEAD) 沒有任何副作用。 此外,如果您啟用跨網域支援,例如 CORS 或 JSONP,則甚至 GET 之類的安全方法可能容易受到 CSRF 攻擊,讓攻擊者能夠讀取潛在的敏感性資料。

ASP.NET MVC 中的防偽造權杖

若要將防偽造權杖新增至 Razor 頁面,請使用 HtmlHelper.AntiForgeryToken 協助程式方法:

@using (Html.BeginForm("Manage", "Account")) {
    @Html.AntiForgeryToken()
}

這個方法會新增隱藏的表單欄位,也會設定 Cookie 權杖。

反 CSRF 和 AJAX

表單權杖可能是 AJAX 要求的問題,因為 AJAX 要求可能會傳送 JSON 資料,而不是 HTML 表單資料。 有一個解決方案是在自訂 HTTP 標頭中傳送權杖。 下列程式碼使用 Razor 語法來產生權杖,然後將權杖新增至 AJAX 要求。 權杖是藉由呼叫 AntiForgery.GetTokens在伺服器上產生。

<script>
    @functions{
        public string TokenHeaderValue()
        {
            string cookieToken, formToken;
            AntiForgery.GetTokens(null, out cookieToken, out formToken);
            return cookieToken + ":" + formToken;                
        }
    }

    $.ajax("api/values", {
        type: "post",
        contentType: "application/json",
        data: {  }, // JSON data goes here
        dataType: "json",
        headers: {
            'RequestVerificationToken': '@TokenHeaderValue()'
        }
    });
</script>

當您處理要求時,請從要求標頭擷取權杖。 然後呼叫 AntiForgery.Validate 方法來驗證權杖。 如果權杖無效, Validate 方法會擲回例外狀況。

void ValidateRequestHeader(HttpRequestMessage request)
{
    string cookieToken = "";
    string formToken = "";

    IEnumerable<string> tokenHeaders;
    if (request.Headers.TryGetValues("RequestVerificationToken", out tokenHeaders))
    {
        string[] tokens = tokenHeaders.First().Split(':');
        if (tokens.Length == 2)
        {
            cookieToken = tokens[0].Trim();
            formToken = tokens[1].Trim();
        }
    }
    AntiForgery.Validate(cookieToken, formToken);
}