防止 ASP.NET 核心中的跨網站偽造要求 (XSRF/CSRF) 攻擊

菲亞茲·哈桑 和裡克· 安德森

跨網站偽造要求是針對 Web 裝載應用程式的攻擊,惡意 Web 應用程式可能會影響用戶端瀏覽器與信任該瀏覽器的 Web 應用程式之間的互動。 這些攻擊是可能的,因為 web 瀏覽器會自動將某些類型的驗證權杖傳送給網站的每個要求。 這種形式的惡意探索也稱為單鍵攻擊會話控制,因為攻擊會利用使用者先前驗證的會話。 跨網站偽造要求也稱為 XSRF 或 CSRF。

CSRF 攻擊的範例:

  1. 使用者使用表單驗證登入 www.good-banking-site.example.com 。 伺服器會驗證使用者,並發出回應,其中包含驗證 cookie。 該網站很容易受到攻擊,因為它信任自己所收到,包含有效驗證 cookie 的任何要求。

  2. 使用者造訪惡意網站 www.bad-crook-site.example.com

    惡意網站 www.bad-crook-site.example.com 包含類似下列範例的 HTML 表單:

    <h1>Congratulations! You're a Winner!</h1>
    <form action="https://good-banking-site.com/api/account" method="post">
        <input type="hidden" name="Transaction" value="withdraw" />
        <input type="hidden" name="Amount" value="1000000" />
        <input type="submit" value="Click to collect your prize!" />
    </form>
    

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

  3. 使用者選取 [提交] 按鈕。 瀏覽器提出要求,並自動包含所要求網域 www.good-banking-site.example.com 的驗證 cookie。

  4. 要求會以使用者的驗證內容在 www.good-banking-site.example.com 伺服器上執行,而且可以執行已驗證使用者允許執行的任何動作。

除了使用者選取按鈕以提交表單的案例之外,惡意網站還可以:

  • 執行自動提交表單的指令碼。
  • 以 AJAX 要求形式傳送表單提交。
  • 使用 CSS 隱藏表單。

除了一開始瀏覽惡意網站之外,這些替代案例並不需要來自使用者的任何動作或輸入。

使用 HTTPS 無法防止 CSRF 攻擊。 惡意網站可以 https://www.good-banking-site.com/ 像傳送不安全的要求一樣輕鬆傳送要求。

某些攻擊的目標是回應 GET 要求的端點,在此情況下,可以使用影像標記來執行動作。 這種形式的攻擊在允許影像但封鎖 JavaScript 的論壇網站上很常見。 發生 GET 要求時會改變狀態 (變數或資源被變更) 的應用程式很容易遭受惡意攻擊。 會改更狀態的 GET 要求是不安全的。 最佳做法是永遠不要在發生 GET 要求時改變狀態。

CSRF 攻擊可能會針對使用 cookie 進行驗證的 Web 應用程式,因為:

  • 瀏覽器會儲存 web 應用程式發出的 cookie。
  • 儲存的 cookie 包含已驗證使用者的會話cookie。
  • 瀏覽器會將與網域關聯的所有 cookie 傳送至 Web 應用程式,不論對應用程式的要求在瀏覽器中產生的方式為何。

然而,CSRF 攻擊並不限於惡意探索 cookie。 例如,基本和摘要式驗證也很容易受到攻擊。 當使用者使用基本或摘要式驗證登入之後,瀏覽器會自動傳送認證,直到會話結束為止。

在此內容中,會話是指使用者經過驗證的用戶端會話。 這與伺服器端會話或 ASP.NET 核心會話中介軟體 無關。

使用者可以採取預防措施來防範 CSRF 弱點:

  • 使用完 Web 應用程式時登出。
  • 定期清除瀏覽器 cookie。

不過,CSRF 弱點基本上是 Web 應用程式,而不是使用者的問題。

驗證基本概念

Cookie 型的驗證是一種熱門的驗證形式。 權杖型驗證系統受歡迎的程度不斷增加,尤其是單頁應用程式 (SPA)。

當使用者使用其使用者名稱和密碼進行驗證時,系統會發出包含驗證票證的權杖。 權杖可用於驗證和授權。 權杖會儲存為 cookie,並隨著用戶端發出的每個要求一起傳送。 使用驗證中介軟體來產生並驗證此 cookie 專案 Cookie 。 中介軟體會將使用者主體序列化為加密的 cookie。 在後續的要求中,中介軟體會驗證 cookie、重新建立主體,並將主體指派給 HttpContext.User 屬性。

權杖型驗證

當使用者經過驗證時,會向他們發出權杖 (不是防偽權杖)。 權杖會以宣告或將應用程式指向該程式所維護使用者狀態的參考權杖的形式包含使用者資訊。 當使用者嘗試存取需要驗證的資源時,權杖會以持有人權杖的形式傳送至具有額外授權標頭的應用程式。 此方法可讓應用程式變成無狀態。 在每個後續要求中,權杖會傳入伺服器端驗證的要求中。 此權杖不會加密;而是會被編碼。 在伺服器上,權杖會被解碼以存取其資訊。 若要在後續的要求上傳送權杖,請將權杖儲存在瀏覽器的本機儲存體中。 將權杖放在瀏覽器本機儲存體中,並加以擷取,並將其視為持有人權杖使用,可保護 CSRF 攻擊。 不過,如果應用程式很容易透過 XSS 或遭入侵的外部 JavaScript 檔案進行腳本插入,攻擊者可以從本機儲存體擷取任何值,並將其傳送給自己。 ASP.NET Core 預設會將變數的所有伺服器端輸出編碼,以降低 XSS 的風險。 如果您使用 Html.Raw 或具有不受信任輸入的自訂程式碼來覆寫此行為 ,可能會增加 XSS 的風險。

如果權杖儲存在瀏覽器的本機儲存體中,請勿擔心 CSRF 弱點。 當權杖儲存於 cookie 時,要小心 CSRF。 如需詳細資訊,請參閱 GitHub 問題 SPA 程式碼範例會新增兩個 cookie

裝載在一個網域的多個應用程式

共用主機環境容易受到會話劫持、登入 CSRF 和其他攻擊的影響。

雖然 example1.contoso.netexample2.contoso.net 是不同的主機,但 *.contoso.net 網域下的主機之間有隱含的信任關係。 此隱含信任關係可讓潛在的不受信任主機影響彼此的 cookie (控管 AJAX 要求的相同原始原則不一定能套用至 HTTP cookie)。

藉由不共用網域,即可防止惡意探索對裝載于相同網域上的應用程式之間的受信任 cookie 進行攻擊。 當每個應用程式裝載在其自己的網域上時,沒有任何隱含的 cookie 信任關係可供惡意探索。

ASP.NET Core 中的防偽

警告

ASP.NET Core 會使用 ASP.NET Core 資料保護來實作防偽。 資料保護堆疊必須設定為在伺服器陣列中運作。 如需詳細資訊,請參閱設定資料保護

Program.cs 中呼叫下列其中一個 API 時,防偽中介軟體會新增至相依性插入容器:

如需詳細資訊,請參閱使用基本 API 進行防偽

FormTagHelper 會將防偽權杖插入 HTML 表單元素中。 Razor 檔案中的下列標記會自動產生防偽權杖:

<form method="post">
    <!-- ... -->
</form>

同樣地,如果表單的方法不是 GET,則 IHtmlHelper.BeginForm 預設會產生防偽權杖。

<form> 標籤包含 method="post" 屬性,且下列任何一項為 true 時,就會自動產生 HTML 表單元素的防偽權杖:

  • 動作屬性是空的 (action="")。
  • 未提供動作屬性 (<form method="post">)。

可以停用自動產生 HTML 表單元素的防偽標記:

  • 使用 asp-antiforgery 屬性明確停用防偽權杖:

    <form method="post" asp-antiforgery="false">
        <!-- ... -->
    </form>
    
  • 使用標籤協助程式 ! 退出符號會使標記退出 :

    <!form method="post">
        <!-- ... -->
    </!form>
    
  • 從檢視中移除 FormTagHelper。 將下列指示詞新增至 Razor 檢視,即可從檢視中移除 FormTagHelper

    @removeTagHelper Microsoft.AspNetCore.Mvc.TagHelpers.FormTagHelper, Microsoft.AspNetCore.Mvc.TagHelpers
    

注意

Razor頁面會自動受到保護,免於 XSRF/CSRF 攻擊。 如需詳細資訊,請參閱 XSRF/CSRF 和 Razor 頁面

防禦 CSRF 攻擊最常見的方法是使用 同步器權杖模式 (STP)。 當使用者要求具有表單資料的頁面時,會使用 STP:

  1. 伺服器會將與目前使用者身分識別關聯的權杖傳送給用戶端。
  2. 用戶端會將權杖傳回伺服器以進行驗證。
  3. 如果伺服器收到不符合已驗證使用者身分識別的權杖,則會拒絕要求。

權杖是唯一且無法預測的。 權杖也可以用來確保一系列要求的適當排序 (例如,確保要求順序為:第 1 頁 > 第 2 頁 > 第 3 頁)。 ASP.NET Core MVC 和 Razor Pages 範本中的所有表單都會產生防偽權杖。 下列一對檢視範例會產生防偽權杖:

<form asp-action="Index" asp-controller="Home" method="post">
    <!-- ... -->
</form>

@using (Html.BeginForm("Index", "Home"))
{
    <!-- ... -->
}

明確地將防偽反權杖新增至 <form> 元素,而不使用標籤協助程式搭配 HTML 協助程式@Html.AntiForgeryToken

<form asp-action="Index" asp-controller="Home" method="post">
    @Html.AntiForgeryToken()

    <!-- ... -->
</form>

在上述每個案例中,ASP.NET Core 會新增類似下列範例的隱藏表單欄位:

<input name="__RequestVerificationToken" type="hidden" value="CfDJ8NrAkS ... s2-m9Yw">

ASP.NET Core 包含三個用於防偽權杖的篩選條件

使用 AddControllers 防偽

呼叫 AddControllers啟用防偽權杖。 必須呼叫 AddControllersWithViews 才能有內建的防偽權杖支援。

多個瀏覽器分頁和同步器權杖模式

使用同步器權杖模式時,只有最近載入的頁面包含有效的防偽權杖。 使用多個分頁可能會有問題。 例如,如果使用者開啟多個分頁:

  • 只有最近載入的分頁包含有效的防偽權杖。
  • 從先前載入的分頁提出的要求失敗,並出現錯誤:Antiforgery token validation failed. The antiforgery cookie token and request token do not match

如果造成問題,請考慮替代 CSRF 保護模式。

使用 AntiforgeryOptions 設定防偽

Program.cs 中自訂 AntiforgeryOptions

builder.Services.AddAntiforgery(options =>
{
    // Set Cookie properties using CookieBuilder properties†.
    options.FormFieldName = "AntiforgeryFieldname";
    options.HeaderName = "X-CSRF-TOKEN-HEADERNAME";
    options.SuppressXFrameOptionsHeader = false;
});

使用 CookieBuilder 類別的屬性來設定防偽 Cookie 屬性,如下表所示。

選項 描述
Cookie 決定用來建立防偽 cookie 的設定。
FormFieldName 防偽系統用來在檢視中轉譯防偽權杖的隱藏表單欄位名稱。
HeaderName 防偽系統所使用的標頭名稱。 如果 null,系統只會考慮表單資料。
SuppressXFrameOptionsHeader 指定是否要隱藏產生 X-Frame-Options 標頭。 根據預設,會產生值為「SAMEORIGIN」的標頭。 預設為 false

如需詳細資訊,請參閱CookieAuthenticationOptions

使用 IAntiforgery 產生防偽權杖

IAntiforgery 提供 API 以設定防偽功能。 您可以使用 WebApplication.Services,在 Program.cs 中要求 IAntiforgery。 下列範例會使用來自應用程式首頁的中介軟體來產生防偽權杖,並在回應中以 cookie 的形式傳送:

app.UseRouting();

app.UseAuthorization();

var antiforgery = app.Services.GetRequiredService<IAntiforgery>();

app.Use((context, next) =>
{
    var requestPath = context.Request.Path.Value;

    if (string.Equals(requestPath, "/", StringComparison.OrdinalIgnoreCase)
        || string.Equals(requestPath, "/index.html", StringComparison.OrdinalIgnoreCase))
    {
        var tokenSet = antiforgery.GetAndStoreTokens(context);
        context.Response.Cookies.Append("XSRF-TOKEN", tokenSet.RequestToken!,
            new CookieOptions { HttpOnly = false });
    }

    return next(context);
});

上述範例會設定名為 XSRF-TOKEN 的 cookie。 用戶端可以讀取此 cookie,並以附加至 AJAX 要求的標頭的形式提供其值。 例如,Angular 包含預設會讀取 cookie (名為 XSRF-TOKEN) 的內建 XSRF 保護

需要防偽驗證

ValidateAntiForgeryToken 動作篩選條件可以套用至個別動作、控制器或全域。 除非要求包含有效的防偽權杖,否則對套用此篩選動作的要求會遭到封鎖:

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Index()
{
    // ...

    return RedirectToAction();
}

ValidateAntiForgeryToken 屬性處理對其所標記動作方法的要求時需要權杖,這些要求包括 HTTP GET 要求。 如果應用程式控制器套用了 ValidateAntiForgeryToken 屬性,則可以使用 IgnoreAntiforgeryToken 屬性將其覆寫。

僅針對不安全的 HTTP 方法自動驗證防偽權杖

您可以使用 AutoValidateAntiforgeryToken 屬性,而非廣泛套用 ValidateAntiForgeryToken 屬性,然後用 IgnoreAntiforgeryToken 屬性將其覆寫。 此屬性的運作方式與 ValidateAntiForgeryToken 屬性相同,不同之處在於它在處理使用下列 HTTP 方法提出的要求時不需要權杖:

  • GET
  • HEAD
  • OPTIONS
  • TRACE

我們建議在非 API 案例中廣泛使用 AutoValidateAntiforgeryToken 。 此屬性可確保 POST 動作預設受到保護。 除非將 ValidateAntiForgeryToken 套用至個別動作方法,否則替代方法預設會忽略防偽權杖。 在此案例中,POST 動作方法可能會因錯誤而不受保護,讓應用程式容易受到 CSRF 攻擊。 所有 POST 都應該傳送防偽權杖。

API 沒有傳送非 cookie 權杖部分的自動機制。 實作可能取決於用戶端程式代碼實作。 以下顯示一些範例:

類別層級範例:

[AutoValidateAntiforgeryToken]
public class HomeController : Controller

全域範例:

builder.Services.AddControllersWithViews(options =>
{
    options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
});

覆寫全域或控制器防偽屬性

IgnoreAntiforgeryToken 篩選條件可用來消除指定動作的防偽權杖需求 (或控制器)。 套用時,此篩選條件會覆寫在較高層級指定的 ValidateAntiForgeryTokenAutoValidateAntiforgeryToken 篩選條件 (全域或控制器上)。

[IgnoreAntiforgeryToken]
public IActionResult IndexOverride()
{
    // ...

    return RedirectToAction();
}

驗證之後重新整理權杖

將使用者重新導向至檢視或 Razor 頁面頁面,而讓使用者通過驗證之後,應該重新整理權杖。

JavaScript、AJAX 和 SPA

在傳統 HTML 型應用程式中,防偽權杖會使用隱藏的表單欄位傳遞至伺服器。 在以新式 JavaScript 為基礎的應用程式和 SPA 中,會以程式設計方式提出許多要求。 這些 AJAX 要求可能會使用其他技術,例如要求標頭或 cookie s 來傳送權杖。

如果使用 cookie 來儲存驗證權杖,並在伺服器上驗證 API 要求,則 CSRF 是潛在的問題。 如果使用本機儲存體來儲存權杖,則可以減緩 CSRF 弱點,因為本機儲存體的值不會隨著每個要求自動傳送到伺服器。 建議使用本機儲存體將防偽權杖儲存在用戶端上,並以要求標頭的形式傳送權杖。

Blazor

如需詳細資訊,請參閱 ASP.NET Core Blazor 驗證和授權

JavaScript

使用 JavaScript 搭配檢視時,可以使用檢視內的服務來建立權杖。 將 IAntiforgery 服務插入檢視中,並呼叫 GetAndStoreTokens

@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Antiforgery

@{
    ViewData["Title"] = "JavaScript";

    var requestToken = Antiforgery.GetAndStoreTokens(Context).RequestToken;
}

<input id="RequestVerificationToken" type="hidden" value="@requestToken" />

<button id="button" class="btn btn-primary">Submit with Token</button>
<div id="result" class="mt-2"></div>

@section Scripts {
<script>
    document.addEventListener("DOMContentLoaded", () => {
        const resultElement = document.getElementById("result");

        document.getElementById("button").addEventListener("click", async () => {

            const response = await fetch("@Url.Action("FetchEndpoint")", {
                method: "POST",
                headers: {
                    RequestVerificationToken:
                        document.getElementById("RequestVerificationToken").value
                }
            });

            if (response.ok) {
                resultElement.innerText = await response.text();
            } else {
                resultElement.innerText = `Request Failed: ${response.status}`
            }
        });
    });
</script>
}

上述範例會使用 JavaScript 讀取 AJAX POST 標頭的隱藏欄位值。

這種方法不需要直接從伺服器處理設定 cookie,或從用戶端讀取它們。 不過,如果無法插入 IAntiforgery 服務,請使用 JavaScript 來存取 cookie 中的權杖:

  • 伺服器額外要求中的存取權杖通常是 same-origin
  • 使用 cookie 的內容來建立具有權杖值的標頭。

假設指令碼會在名為 X-XSRF-TOKEN 的要求標頭中傳送權杖,請設定防偽服務來尋找 X-XSRF-TOKEN 標頭:

builder.Services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN");

下列範例會新增受保護的端點,將要求權杖寫入 JavaScript 可讀取的 cookie:

app.UseAuthorization();
app.MapGet("antiforgery/token", (IAntiforgery forgeryService, HttpContext context) =>
{
    var tokens = forgeryService.GetAndStoreTokens(context);
    context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken!,
            new CookieOptions { HttpOnly = false });

    return Results.Ok();
}).RequireAuthorization();

下列範例會使用 JavaScript 提出 AJAX 要求,以取得權杖,並使用適當的標頭提出另一個要求:

var response = await fetch("/antiforgery/token", {
    method: "GET",
    headers: { "Authorization": authorizationToken }
});

if (response.ok) {
    // https://developer.mozilla.org/docs/web/api/document/cookie
    const xsrfToken = document.cookie
        .split("; ")
        .find(row => row.startsWith("XSRF-TOKEN="))
        .split("=")[1];

    response = await fetch("/JavaScript/FetchEndpoint", {
        method: "POST",
        headers: { "X-XSRF-TOKEN": xsrfToken, "Authorization": authorizationToken }
    });

    if (response.ok) {
        resultElement.innerText = await response.text();
    } else {
        resultElement.innerText = `Request Failed: ${response.status}`
    }
} else {    
    resultElement.innerText = `Request Failed: ${response.status}`
}

注意

當要求標頭和表單承載中都提供防偽權杖時,只會驗證標頭中的權杖。

使用基本 API 的防偽

呼叫 AddAntiforgery ,並在 UseAntiforgery(IApplicationBuilder) DI 中註冊反forgery 服務。 防偽權杖可用來減輕跨網站偽造要求攻擊

var builder = WebApplication.CreateBuilder();

builder.Services.AddAntiforgery();

var app = builder.Build();

app.UseAntiforgery();

app.MapGet("/", () => "Hello World!");

app.Run();

防偽中介軟體:

只有在下列狀況下,才會驗證防偽權杖:

  • 端點包含實作 IAntiforgeryMetadata 的中繼資料,其中 RequiresValidation=true
  • 與端點相關聯的 HTTP 方法是相關的 HTTP 方法。 相關方法是 TRACE、OPTIONS、HEAD 和 GET 以外的所有 HTTP 方法
  • 要求與有效的端點相關聯。

注意: 手動啟用時,驗證和授權中介軟體之後必須執行防偽中介軟體,以防止使用者未經驗證時讀取表單資料。

根據預設,接受表單資料的最小 API 需要防偽權杖驗證。

請考慮下列 GenerateForm 方法:

public static string GenerateForm(string action, 
    AntiforgeryTokenSet token, bool UseToken=true)
{
    string tokenInput = "";
    if (UseToken)
    {
        tokenInput = $@"<input name=""{token.FormFieldName}""
                         type=""hidden"" value=""{token.RequestToken}"" />";
    }

    return $@"
    <html><body>
        <form action=""{action}"" method=""POST"" enctype=""multipart/form-data"">
            {tokenInput}
            <input type=""text"" name=""name"" />
            <input type=""date"" name=""dueDate"" />
            <input type=""checkbox"" name=""isCompleted"" />
            <input type=""submit"" />
        </form>
    </body></html>
";
}

上述程式碼有三個引數:動作、防偽權杖,以及指出是否應該使用權杖的 bool

請考慮下列範例:

using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder();

builder.Services.AddAntiforgery();

var app = builder.Build();

app.UseAntiforgery();

// Pass token
app.MapGet("/", (HttpContext context, IAntiforgery antiforgery) =>
{
    var token = antiforgery.GetAndStoreTokens(context);
    return Results.Content(MyHtml.GenerateForm("/todo", token), "text/html");
});

// Don't pass a token, fails
app.MapGet("/SkipToken", (HttpContext context, IAntiforgery antiforgery) =>
{
    var token = antiforgery.GetAndStoreTokens(context);
    return Results.Content(MyHtml.GenerateForm("/todo",token, false ), "text/html");
});

// Post to /todo2. DisableAntiforgery on that endpoint so no token needed.
app.MapGet("/DisableAntiforgery", (HttpContext context, IAntiforgery antiforgery) =>
{
    var token = antiforgery.GetAndStoreTokens(context);
    return Results.Content(MyHtml.GenerateForm("/todo2", token, false), "text/html");
});

app.MapPost("/todo", ([FromForm] Todo todo) => Results.Ok(todo));

app.MapPost("/todo2", ([FromForm] Todo todo) => Results.Ok(todo))
                                                .DisableAntiforgery();

app.Run();

class Todo
{
    public required string Name { get; set; }
    public bool IsCompleted { get; set; }
    public DateTime DueDate { get; set; }
}

public static class MyHtml
{
    public static string GenerateForm(string action, 
        AntiforgeryTokenSet token, bool UseToken=true)
    {
        string tokenInput = "";
        if (UseToken)
        {
            tokenInput = $@"<input name=""{token.FormFieldName}""
                             type=""hidden"" value=""{token.RequestToken}"" />";
        }

        return $@"
        <html><body>
            <form action=""{action}"" method=""POST"" enctype=""multipart/form-data"">
                {tokenInput}
                <input type=""text"" name=""name"" />
                <input type=""date"" name=""dueDate"" />
                <input type=""checkbox"" name=""isCompleted"" />
                <input type=""submit"" />
            </form>
        </body></html>
    ";
    }
}

在上述程式碼中,張貼至:

  • /todo 需要有效的防偽權杖。
  • /todo2需要有效的防偽權杖,因為呼叫了 DisableAntiforgery
app.MapPost("/todo", ([FromForm] Todo todo) => Results.Ok(todo));

app.MapPost("/todo2", ([FromForm] Todo todo) => Results.Ok(todo))
                                                .DisableAntiforgery();

POST 至:

  • / 端點產生的表單 /todo 會成功,因為防偽權杖有效。
  • /SkipToken 產生的表單 /todo 則失敗了,因為不包含防偽資訊。
  • /DisableAntiforgery 端點產生的表單 /todo2 會成功,因為防偽權杖有效。
app.MapPost("/todo", ([FromForm] Todo todo) => Results.Ok(todo));

app.MapPost("/todo2", ([FromForm] Todo todo) => Results.Ok(todo))
                                                .DisableAntiforgery();

提交表單時,沒有有效的防偽反forgery 權杖:

  • 在開發環境中,擲回例外狀況。
  • 在生產環境中,會記錄訊息。

Windows 驗證和防偽 cookie

使用 Windows 驗證時,應用程式端點必須受到保護,以防止 CSRF 攻擊的方式,就像針對 cookie 所做的一樣。 瀏覽器會隱含地將驗證內容傳送至伺服器,而且必須保護端點免於 CSRF 攻擊。

擴充防偽

IAntiforgeryAdditionalDataProvider 類型可讓開發人員藉由往返每個權杖中的其他資料來擴充反 CSRF 系統的行為。 每次產生欄位權杖時都會呼叫 GetAdditionalData 方法,而且傳回值會內嵌在產生的權杖中。 實作者可以傳回時間戳記、nonce 或任何其他值,然後在驗證權杖時呼叫 ValidateAdditionalData 來驗證此資料。 用戶端的使用者名稱已經內嵌在產生的權杖中,因此不需要包含這項資訊。 如果權杖包含補充資料,但沒有 IAntiForgeryAdditionalDataProvider 設定,則不會驗證補充資料。

其他資源

跨網站偽造要求 (也稱為 XSRF 或 CSRF) 是針對 Web 裝載應用程式的攻擊,惡意 Web 應用程式可能會藉此影響用戶端瀏覽器與信任該瀏覽器的 Web 應用程式之間的互動。 這些攻擊是可能的,因為 web 瀏覽器會自動將某些類型的驗證權杖傳送給網站的每個要求。 這種形式的惡意探索也稱為單鍵攻擊會話控制,因為攻擊會利用使用者先前驗證的會話。

CSRF 攻擊的範例:

  1. 使用者使用表單驗證登入 www.good-banking-site.example.com 。 伺服器會驗證使用者,並發出回應,其中包含驗證 cookie。 該網站很容易受到攻擊,因為它信任自己所收到,包含有效驗證 cookie 的任何要求。

  2. 使用者造訪惡意網站 www.bad-crook-site.example.com

    惡意網站 www.bad-crook-site.example.com 包含類似下列範例的 HTML 表單:

    <h1>Congratulations! You're a Winner!</h1>
    <form action="https://good-banking-site.com/api/account" method="post">
        <input type="hidden" name="Transaction" value="withdraw" />
        <input type="hidden" name="Amount" value="1000000" />
        <input type="submit" value="Click to collect your prize!" />
    </form>
    

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

  3. 使用者選取 [提交] 按鈕。 瀏覽器提出要求,並自動包含所要求網域 www.good-banking-site.example.com 的驗證 cookie。

  4. 要求會以使用者的驗證內容在 www.good-banking-site.example.com 伺服器上執行,而且可以執行已驗證使用者允許執行的任何動作。

除了使用者選取按鈕以提交表單的案例之外,惡意網站還可以:

  • 執行自動提交表單的指令碼。
  • 以 AJAX 要求形式傳送表單提交。
  • 使用 CSS 隱藏表單。

除了一開始瀏覽惡意網站之外,這些替代案例並不需要來自使用者的任何動作或輸入。

使用 HTTPS 無法防止 CSRF 攻擊。 惡意網站可以傳送 https://www.good-banking-site.com/ 要求,就像傳送不安全的要求一樣容易。

某些攻擊的目標是回應 GET 要求的端點,在此情況下,可以使用影像標記來執行動作。 這種形式的攻擊在允許影像但封鎖 JavaScript 的論壇網站上很常見。 發生 GET 要求時會改變狀態 (變數或資源被變更) 的應用程式很容易遭受惡意攻擊。 會改更狀態的 GET 要求是不安全的。 最佳做法是永遠不要在發生 GET 要求時改變狀態。

CSRF 攻擊可能會針對使用 cookie 進行驗證的 Web 應用程式,因為:

  • 瀏覽器會儲存 web 應用程式發出的 cookie。
  • 儲存的 cookie 包含已驗證使用者的會話cookie。
  • 瀏覽器會將與網域關聯的所有 cookie 傳送至 Web 應用程式,不論對應用程式的要求在瀏覽器中產生的方式為何。

然而,CSRF 攻擊並不限於惡意探索 cookie。 例如,基本和摘要式驗證也很容易受到攻擊。 當使用者使用基本或摘要式驗證登入之後,瀏覽器會自動傳送認證,直到會話結束為止。

在此內容中,會話是指使用者經過驗證的用戶端會話。 這與伺服器端會話或 ASP.NET 核心會話中介軟體 無關。

使用者可以採取預防措施來防範 CSRF 弱點:

  • 使用完 Web 應用程式時登出。
  • 定期清除瀏覽器 cookie。

不過,CSRF 弱點基本上是 Web 應用程式,而不是使用者的問題。

驗證基本概念

Cookie 型的驗證是一種熱門的驗證形式。 權杖型驗證系統受歡迎的程度不斷增加,尤其是單頁應用程式 (SPA)。

當使用者使用其使用者名稱和密碼進行驗證時,會向他們發出權杖,其中包含可用於驗證和授權的驗證票證。 權杖會儲存為 cookie,並隨著用戶端發出的每個要求一起傳送。 Cookie 驗證中介軟體會產生和驗證此 cookie。 中介軟體會將使用者主體序列化為加密的 cookie。 在後續的要求中,中介軟體會驗證 cookie、重新建立主體,並將主體指派給 HttpContext.User 屬性。

權杖型驗證

當使用者經過驗證時,會向他們發出權杖 (不是防偽權杖)。 權杖會以宣告或將應用程式指向該程式所維護使用者狀態的參考權杖的形式包含使用者資訊。 當使用者嘗試存取需要驗證的資源時,權杖會以持有人權杖的形式傳送至具有額外授權標頭的應用程式。 此方法可讓應用程式變成無狀態。 在每個後續要求中,權杖會傳入伺服器端驗證的要求中。 此權杖不會加密;而是會被編碼。 在伺服器上,權杖會被解碼以存取其資訊。 若要在後續的要求上傳送權杖,請將權杖儲存在瀏覽器的本機儲存體中。 將權杖放在瀏覽器本機儲存體中,並加以擷取,並將其視為持有人權杖使用,可保護 CSRF 攻擊。 不過,如果應用程式很容易透過 XSS 或遭入侵的外部 javascript 檔案進行指令碼插入,攻擊者可以從本機儲存體擷取任何值,並將其傳送給自己。 ASP.NET Core 預設會將變數的所有伺服器端輸出編碼,以降低 XSS 的風險。 如果您使用 Html.Raw 或具有不受信任輸入的自訂程式碼來覆寫此行為 ,可能會增加 XSS 的風險。

如果權杖儲存在瀏覽器的本機儲存體中,請勿擔心 CSRF 弱點。 當權杖儲存於 cookie 時,要小心 CSRF。 如需詳細資訊,請參閱 GitHub 問題 SPA 程式碼範例會新增兩個 cookie

裝載在一個網域的多個應用程式

共用主機環境容易受到會話劫持、登入 CSRF 和其他攻擊的影響。

雖然 example1.contoso.netexample2.contoso.net 是不同的主機,但 *.contoso.net 網域下的主機之間有隱含的信任關係。 此隱含信任關係可讓潛在的不受信任主機影響彼此的 cookie (控管 AJAX 要求的相同原始原則不一定能套用至 HTTP cookie)。

藉由不共用網域,即可防止惡意探索對裝載于相同網域上的應用程式之間的受信任 cookie 進行攻擊。 當每個應用程式裝載在其自己的網域上時,沒有任何隱含的 cookie 信任關係可供惡意探索。

ASP.NET Core 中的防偽

警告

ASP.NET Core 會使用 ASP.NET Core 資料保護來實作防偽。 資料保護堆疊必須設定為在伺服器陣列中運作。 如需詳細資訊,請參閱設定資料保護

Program.cs 中呼叫下列其中一個 API 時,防偽中介軟體會新增至相依性插入容器:

FormTagHelper 會將防偽權杖插入 HTML 表單元素中。 Razor 檔案中的下列標記會自動產生防偽權杖:

<form method="post">
    <!-- ... -->
</form>

同樣地,如果表單的方法不是 GET,則 IHtmlHelper.BeginForm 預設會產生防偽權杖。

<form> 標籤包含 method="post" 屬性,且下列任何一項為 true 時,就會自動產生 HTML 表單元素的防偽權杖:

  • 動作屬性是空的 (action="")。
  • 未提供動作屬性 (<form method="post">)。

可以停用自動產生 HTML 表單元素的防偽標記:

  • 使用 asp-antiforgery 屬性明確停用防偽權杖:

    <form method="post" asp-antiforgery="false">
        <!-- ... -->
    </form>
    
  • 使用標籤協助程式 ! 退出符號會使標記退出 :

    <!form method="post">
        <!-- ... -->
    </!form>
    
  • 從檢視中移除 FormTagHelper。 將下列指示詞新增至 Razor 檢視,即可從檢視中移除 FormTagHelper

    @removeTagHelper Microsoft.AspNetCore.Mvc.TagHelpers.FormTagHelper, Microsoft.AspNetCore.Mvc.TagHelpers
    

注意

Razor頁面會自動受到保護,免於 XSRF/CSRF 攻擊。 如需詳細資訊,請參閱 XSRF/CSRF 和 Razor 頁面

防禦 CSRF 攻擊最常見的方法是使用 同步器權杖模式 (STP)。 當使用者要求具有表單資料的頁面時,會使用 STP:

  1. 伺服器會將與目前使用者身分識別關聯的權杖傳送給用戶端。
  2. 用戶端會將權杖傳回伺服器以進行驗證。
  3. 如果伺服器收到不符合已驗證使用者身分識別的權杖,則會拒絕要求。

權杖是唯一且無法預測的。 權杖也可以用來確保一系列要求的適當排序 (例如,確保要求順序為:第 1 頁 > 第 2 頁 > 第 3 頁)。 ASP.NET Core MVC 和 Razor Pages 範本中的所有表單都會產生防偽權杖。 下列一對檢視範例會產生防偽權杖:

<form asp-action="Index" asp-controller="Home" method="post">
    <!-- ... -->
</form>

@using (Html.BeginForm("Index", "Home"))
{
    <!-- ... -->
}

明確地將防偽反權杖新增至 <form> 元素,而不使用標籤協助程式搭配 HTML 協助程式@Html.AntiForgeryToken

<form asp-action="Index" asp-controller="Home" method="post">
    @Html.AntiForgeryToken()

    <!-- ... -->
</form>

在上述每個案例中,ASP.NET Core 會新增類似下列範例的隱藏表單欄位:

<input name="__RequestVerificationToken" type="hidden" value="CfDJ8NrAkS ... s2-m9Yw">

ASP.NET Core 包含三個用於防偽權杖的篩選條件

使用 AddControllers 防偽

呼叫 AddControllers啟用防偽權杖。 必須呼叫 AddControllersWithViews 才能有內建的防偽權杖支援。

多個瀏覽器分頁和同步器權杖模式

使用同步器權杖模式時,只有最近載入的頁面包含有效的防偽權杖。 使用多個分頁可能會有問題。 例如,如果使用者開啟多個分頁:

  • 只有最近載入的分頁包含有效的防偽權杖。
  • 從先前載入的分頁提出的要求失敗,並出現錯誤:Antiforgery token validation failed. The antiforgery cookie token and request token do not match

如果造成問題,請考慮替代 CSRF 保護模式。

使用 AntiforgeryOptions 設定防偽

Program.cs 中自訂 AntiforgeryOptions

builder.Services.AddAntiforgery(options =>
{
    // Set Cookie properties using CookieBuilder properties†.
    options.FormFieldName = "AntiforgeryFieldname";
    options.HeaderName = "X-CSRF-TOKEN-HEADERNAME";
    options.SuppressXFrameOptionsHeader = false;
});

使用 CookieBuilder 類別的屬性來設定防偽 Cookie 屬性,如下表所示。

選項 描述
Cookie 決定用來建立防偽 cookie 的設定。
FormFieldName 防偽系統用來在檢視中轉譯防偽權杖的隱藏表單欄位名稱。
HeaderName 防偽系統所使用的標頭名稱。 如果 null,系統只會考慮表單資料。
SuppressXFrameOptionsHeader 指定是否要隱藏產生 X-Frame-Options 標頭。 根據預設,會產生值為「SAMEORIGIN」的標頭。 預設為 false

如需詳細資訊,請參閱CookieAuthenticationOptions

使用 IAntiforgery 產生防偽權杖

IAntiforgery 提供 API 以設定防偽功能。 您可以使用 WebApplication.Services,在 Program.cs 中要求 IAntiforgery。 下列範例會使用來自應用程式首頁的中介軟體來產生防偽權杖,並在回應中以 cookie 的形式傳送:

app.UseRouting();

app.UseAuthorization();

var antiforgery = app.Services.GetRequiredService<IAntiforgery>();

app.Use((context, next) =>
{
    var requestPath = context.Request.Path.Value;

    if (string.Equals(requestPath, "/", StringComparison.OrdinalIgnoreCase)
        || string.Equals(requestPath, "/index.html", StringComparison.OrdinalIgnoreCase))
    {
        var tokenSet = antiforgery.GetAndStoreTokens(context);
        context.Response.Cookies.Append("XSRF-TOKEN", tokenSet.RequestToken!,
            new CookieOptions { HttpOnly = false });
    }

    return next(context);
});

上述範例會設定名為 XSRF-TOKEN 的 cookie。 用戶端可以讀取此 cookie,並以附加至 AJAX 要求的標頭的形式提供其值。 例如,Angular 包含預設會讀取 cookie (名為 XSRF-TOKEN) 的內建 XSRF 保護

需要防偽驗證

ValidateAntiForgeryToken 動作篩選條件可以套用至個別動作、控制器或全域。 除非要求包含有效的防偽權杖,否則對套用此篩選動作的要求會遭到封鎖:

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Index()
{
    // ...

    return RedirectToAction();
}

ValidateAntiForgeryToken 屬性處理對其所標記動作方法的要求時需要權杖,這些要求包括 HTTP GET 要求。 如果應用程式控制器套用了 ValidateAntiForgeryToken 屬性,則可以使用 IgnoreAntiforgeryToken 屬性將其覆寫。

僅針對不安全的 HTTP 方法自動驗證防偽權杖

您可以使用 AutoValidateAntiforgeryToken 屬性,而非廣泛套用 ValidateAntiForgeryToken 屬性,然後用 IgnoreAntiforgeryToken 屬性將其覆寫。 此屬性的運作方式與 ValidateAntiForgeryToken 屬性相同,不同之處在於它在處理使用下列 HTTP 方法提出的要求時不需要權杖:

  • GET
  • HEAD
  • OPTIONS
  • TRACE

我們建議在非 API 案例中廣泛使用 AutoValidateAntiforgeryToken 。 此屬性可確保 POST 動作預設受到保護。 除非將 ValidateAntiForgeryToken 套用至個別動作方法,否則替代方法預設會忽略防偽權杖。 在此案例中,POST 動作方法可能會因錯誤而不受保護,讓應用程式容易受到 CSRF 攻擊。 所有 POST 都應該傳送防偽權杖。

API 沒有傳送非 cookie 權杖部分的自動機制。 實作可能取決於用戶端程式代碼實作。 以下顯示一些範例:

類別層級範例:

[AutoValidateAntiforgeryToken]
public class HomeController : Controller

全域範例:

builder.Services.AddControllersWithViews(options =>
{
    options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
});

覆寫全域或控制器防偽屬性

IgnoreAntiforgeryToken 篩選條件可用來消除指定動作的防偽權杖需求 (或控制器)。 套用時,此篩選條件會覆寫在較高層級指定的 ValidateAntiForgeryTokenAutoValidateAntiforgeryToken 篩選條件 (全域或控制器上)。

[IgnoreAntiforgeryToken]
public IActionResult IndexOverride()
{
    // ...

    return RedirectToAction();
}

驗證之後重新整理權杖

將使用者重新導向至檢視或 Razor 頁面頁面,而讓使用者通過驗證之後,應該重新整理權杖。

JavaScript、AJAX 和 SPA

在傳統 HTML 型應用程式中,防偽權杖會使用隱藏的表單欄位傳遞至伺服器。 在以新式 JavaScript 為基礎的應用程式和 SPA 中,會以程式設計方式提出許多要求。 這些 AJAX 要求可能會使用其他技術 (例如要求標頭或 cookie) 來傳送權杖。

如果使用 cookie 來儲存驗證權杖,並在伺服器上驗證 API 要求,則 CSRF 是潛在的問題。 如果使用本機儲存體來儲存權杖,則可以減緩 CSRF 弱點,因為本機儲存體的值不會隨著每個要求自動傳送到伺服器。 建議使用本機儲存體將防偽權杖儲存在用戶端上,並以要求標頭的形式傳送權杖。

JavaScript

使用 JavaScript 搭配檢視時,可以使用檢視內的服務來建立權杖。 將 IAntiforgery 服務插入檢視中,並呼叫 GetAndStoreTokens

@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Antiforgery

@{
    ViewData["Title"] = "JavaScript";

    var requestToken = Antiforgery.GetAndStoreTokens(Context).RequestToken;
}

<input id="RequestVerificationToken" type="hidden" value="@requestToken" />

<button id="button" class="btn btn-primary">Submit with Token</button>
<div id="result" class="mt-2"></div>

@section Scripts {
<script>
    document.addEventListener("DOMContentLoaded", () => {
        const resultElement = document.getElementById("result");

        document.getElementById("button").addEventListener("click", async () => {

            const response = await fetch("@Url.Action("FetchEndpoint")", {
                method: "POST",
                headers: {
                    RequestVerificationToken:
                        document.getElementById("RequestVerificationToken").value
                }
            });

            if (response.ok) {
                resultElement.innerText = await response.text();
            } else {
                resultElement.innerText = `Request Failed: ${response.status}`
            }
        });
    });
</script>
}

上述範例會使用 JavaScript 讀取 AJAX POST 標頭的隱藏欄位值。

這種方法不需要直接從伺服器處理設定 cookie,或從用戶端讀取它們。 不過,如果無法插入 IAntiforgery 服務,請使用 JavaScript 來存取 cookie 中的權杖:

  • 伺服器額外要求中的存取權杖通常是 same-origin
  • 使用 cookie 的內容來建立具有權杖值的標頭。

假設指令碼會在名為 X-XSRF-TOKEN 的要求標頭中傳送權杖,請設定防偽服務來尋找 X-XSRF-TOKEN 標頭:

builder.Services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN");

下列範例會新增受保護的端點,將要求權杖寫入 JavaScript 可讀取的 cookie:

app.UseAuthorization();
app.MapGet("antiforgery/token", (IAntiforgery forgeryService, HttpContext context) =>
{
    var tokens = forgeryService.GetAndStoreTokens(context);
    context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken!,
            new CookieOptions { HttpOnly = false });

    return Results.Ok();
}).RequireAuthorization();

下列範例會使用 JavaScript 提出 AJAX 要求,以取得權杖,並使用適當的標頭提出另一個要求:

var response = await fetch("/antiforgery/token", {
    method: "GET",
    headers: { "Authorization": authorizationToken }
});

if (response.ok) {
    // https://developer.mozilla.org/docs/web/api/document/cookie
    const xsrfToken = document.cookie
        .split("; ")
        .find(row => row.startsWith("XSRF-TOKEN="))
        .split("=")[1];

    response = await fetch("/JavaScript/FetchEndpoint", {
        method: "POST",
        headers: { "X-XSRF-TOKEN": xsrfToken, "Authorization": authorizationToken }
    });

    if (response.ok) {
        resultElement.innerText = await response.text();
    } else {
        resultElement.innerText = `Request Failed: ${response.status}`
    }
} else {    
    resultElement.innerText = `Request Failed: ${response.status}`
}

注意

當要求標頭和表單承載中都提供防偽權杖時,只會驗證標頭中的權杖。

使用基本 API 的防偽

Minimal APIs 不支援使用包含的篩選條件 (ValidateAntiForgeryTokenAutoValidateAntiforgeryTokenIgnoreAntiforgeryToken),不過 IAntiforgery 會提供必要的 API 來驗證要求。

下列範例會建立驗證防偽權杖的篩選條件:

internal static class AntiForgeryExtensions
{
    public static TBuilder ValidateAntiforgery<TBuilder>(this TBuilder builder) where TBuilder : IEndpointConventionBuilder
    {
        return builder.AddEndpointFilter(routeHandlerFilter: async (context, next) =>
        {
            try
            {
                var antiForgeryService = context.HttpContext.RequestServices.GetRequiredService<IAntiforgery>();
                await antiForgeryService.ValidateRequestAsync(context.HttpContext);
            }
            catch (AntiforgeryValidationException)
            {
                return Results.BadRequest("Antiforgery token validation failed.");
            }

            return await next(context);

        });
    }
}

然後,篩選條件可以套用至端點:

app.MapPost("api/upload", (IFormFile name) => Results.Accepted())
    .RequireAuthorization()
    .ValidateAntiforgery();

Windows 驗證和防偽 cookie

使用 Windows 驗證時,應用程式端點必須受到保護,以防止 CSRF 攻擊的方式,就像針對 cookie 所做的一樣。 瀏覽器會隱含地將驗證內容傳送至伺服器,而且必須保護端點免於 CSRF 攻擊。

擴充防偽

IAntiforgeryAdditionalDataProvider 類型可讓開發人員藉由往返每個權杖中的其他資料來擴充反 CSRF 系統的行為。 每次產生欄位權杖時都會呼叫 GetAdditionalData 方法,而且傳回值會內嵌在產生的權杖中。 實作者可以傳回時間戳記、nonce 或任何其他值,然後在驗證權杖時呼叫 ValidateAdditionalData 來驗證此資料。 用戶端的使用者名稱已經內嵌在產生的權杖中,因此不需要包含這項資訊。 如果權杖包含補充資料,但沒有 IAntiForgeryAdditionalDataProvider 設定,則不會驗證補充資料。

其他資源

跨網站偽造要求 (也稱為 XSRF 或 CSRF) 是針對 Web 裝載應用程式的攻擊,惡意 Web 應用程式可能會藉此影響用戶端瀏覽器與信任該瀏覽器的 Web 應用程式之間的互動。 這些攻擊是可能的,因為 web 瀏覽器會自動將某些類型的驗證權杖傳送給網站的每個要求。 這種形式的惡意探索也稱為單鍵攻擊會話控制,因為攻擊會利用使用者先前驗證的會話。

CSRF 攻擊的範例:

  1. 使用者使用表單驗證登入 www.good-banking-site.example.com 。 伺服器會驗證使用者,並發出回應,其中包含驗證 cookie。 該網站很容易受到攻擊,因為它信任自己所收到,包含有效驗證 cookie 的任何要求。

  2. 使用者造訪惡意網站 www.bad-crook-site.example.com

    惡意網站 www.bad-crook-site.example.com 包含類似下列範例的 HTML 表單:

    <h1>Congratulations! You're a Winner!</h1>
    <form action="https://good-banking-site.com/api/account" method="post">
        <input type="hidden" name="Transaction" value="withdraw" />
        <input type="hidden" name="Amount" value="1000000" />
        <input type="submit" value="Click to collect your prize!" />
    </form>
    

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

  3. 使用者選取 [提交] 按鈕。 瀏覽器提出要求,並自動包含所要求網域 www.good-banking-site.example.com 的驗證 cookie。

  4. 要求會以使用者的驗證內容在 www.good-banking-site.example.com 伺服器上執行,而且可以執行已驗證使用者允許執行的任何動作。

除了使用者選取按鈕以提交表單的案例之外,惡意網站還可以:

  • 執行自動提交表單的指令碼。
  • 以 AJAX 要求形式傳送表單提交。
  • 使用 CSS 隱藏表單。

除了一開始瀏覽惡意網站之外,這些替代案例並不需要來自使用者的任何動作或輸入。

使用 HTTPS 無法防止 CSRF 攻擊。 惡意網站可以傳送 https://www.good-banking-site.com/ 要求,就像傳送不安全的要求一樣容易。

某些攻擊的目標是回應 GET 要求的端點,在此情況下,可以使用影像標記來執行動作。 這種形式的攻擊在允許影像但封鎖 JavaScript 的論壇網站上很常見。 發生 GET 要求時會改變狀態 (變數或資源被變更) 的應用程式很容易遭受惡意攻擊。 會改更狀態的 GET 要求是不安全的。 最佳做法是永遠不要在發生 GET 要求時改變狀態。

CSRF 攻擊可能會針對使用 cookie 進行驗證的 Web 應用程式,因為:

  • 瀏覽器會儲存 web 應用程式發出的 cookie。
  • 儲存的 cookie 包含已驗證使用者的會話cookie。
  • 瀏覽器會將與網域關聯的所有 cookie 傳送至 Web 應用程式,不論對應用程式的要求在瀏覽器中產生的方式為何。

然而,CSRF 攻擊並不限於惡意探索 cookie。 例如,基本和摘要式驗證也很容易受到攻擊。 當使用者使用基本或摘要式驗證登入之後,瀏覽器會自動傳送認證,直到會話結束為止。

在此內容中,會話是指使用者經過驗證的用戶端會話。 這與伺服器端會話或 ASP.NET 核心會話中介軟體 無關。

使用者可以採取預防措施來防範 CSRF 弱點:

  • 使用完 Web 應用程式時登出。
  • 定期清除瀏覽器 cookie。

不過,CSRF 弱點基本上是 Web 應用程式,而不是使用者的問題。

驗證基本概念

Cookie 型的驗證是一種熱門的驗證形式。 權杖型驗證系統受歡迎的程度不斷增加,尤其是單頁應用程式 (SPA)。

當使用者使用其使用者名稱和密碼進行驗證時,會向他們發出權杖,其中包含可用於驗證和授權的驗證票證。 權杖會儲存為 cookie,並隨著用戶端發出的每個要求一起傳送。 Cookie 驗證中介軟體會產生和驗證此 cookie。 中介軟體會將使用者主體序列化為加密的 cookie。 在後續的要求中,中介軟體會驗證 cookie、重新建立主體,並將主體指派給 HttpContext.User 屬性。

權杖型驗證

當使用者經過驗證時,會向他們發出權杖 (不是防偽權杖)。 權杖會以宣告或將應用程式指向該程式所維護使用者狀態的參考權杖的形式包含使用者資訊。 當使用者嘗試存取需要驗證的資源時,權杖會以持有人權杖的形式傳送至具有額外授權標頭的應用程式。 此方法可讓應用程式變成無狀態。 在每個後續要求中,權杖會傳入伺服器端驗證的要求中。 此權杖不會加密;而是會被編碼。 在伺服器上,權杖會被解碼以存取其資訊。 若要在後續的要求上傳送權杖,請將權杖儲存在瀏覽器的本機儲存體中。 如果權杖儲存在瀏覽器的本機儲存體中,請勿擔心 CSRF 弱點。 當權杖儲存於 cookie 時,要小心 CSRF。 如需詳細資訊,請參閱 GitHub 問題 SPA 程式碼範例會新增兩個 cookie

裝載在一個網域的多個應用程式

共用主機環境容易受到會話劫持、登入 CSRF 和其他攻擊的影響。

雖然 example1.contoso.netexample2.contoso.net 是不同的主機,但 *.contoso.net 網域下的主機之間有隱含的信任關係。 此隱含信任關係可讓潛在的不受信任主機影響彼此的 cookie (控管 AJAX 要求的相同原始原則不一定能套用至 HTTP cookie)。

藉由不共用網域,即可防止惡意探索對裝載于相同網域上的應用程式之間的受信任 cookie 進行攻擊。 當每個應用程式裝載在其自己的網域上時,沒有任何隱含的 cookie 信任關係可供惡意探索。

ASP.NET Core 中的防偽

警告

ASP.NET Core 會使用 ASP.NET Core 資料保護來實作防偽。 資料保護堆疊必須設定為在伺服器陣列中運作。 如需詳細資訊,請參閱設定資料保護

Program.cs 中呼叫下列其中一個 API 時,防偽中介軟體會新增至相依性插入容器:

FormTagHelper 會將防偽權杖插入 HTML 表單元素中。 Razor 檔案中的下列標記會自動產生防偽權杖:

<form method="post">
    <!-- ... -->
</form>

同樣地,如果表單的方法不是 GET,則 IHtmlHelper.BeginForm 預設會產生防偽權杖。

<form> 標籤包含 method="post" 屬性,且下列任何一項為 true 時,就會自動產生 HTML 表單元素的防偽權杖:

  • 動作屬性是空的 (action="")。
  • 未提供動作屬性 (<form method="post">)。

可以停用自動產生 HTML 表單元素的防偽標記:

  • 使用 asp-antiforgery 屬性明確停用防偽權杖:

    <form method="post" asp-antiforgery="false">
        <!-- ... -->
    </form>
    
  • 使用標籤協助程式 ! 退出符號會使標記退出 :

    <!form method="post">
        <!-- ... -->
    </!form>
    
  • 從檢視中移除 FormTagHelper。 將下列指示詞新增至 Razor 檢視,即可從檢視中移除 FormTagHelper

    @removeTagHelper Microsoft.AspNetCore.Mvc.TagHelpers.FormTagHelper, Microsoft.AspNetCore.Mvc.TagHelpers
    

注意

Razor頁面會自動受到保護,免於 XSRF/CSRF 攻擊。 如需詳細資訊,請參閱 XSRF/CSRF 和 Razor 頁面

防禦 CSRF 攻擊最常見的方法是使用 同步器權杖模式 (STP)。 當使用者要求具有表單資料的頁面時,會使用 STP:

  1. 伺服器會將與目前使用者身分識別關聯的權杖傳送給用戶端。
  2. 用戶端會將權杖傳回伺服器以進行驗證。
  3. 如果伺服器收到不符合已驗證使用者身分識別的權杖,則會拒絕要求。

權杖是唯一且無法預測的。 權杖也可以用來確保一系列要求的適當排序 (例如,確保要求順序為:第 1 頁 > 第 2 頁 > 第 3 頁)。 ASP.NET Core MVC 和 Razor Pages 範本中的所有表單都會產生防偽權杖。 下列一對檢視範例會產生防偽權杖:

<form asp-action="Index" asp-controller="Home" method="post">
    <!-- ... -->
</form>

@using (Html.BeginForm("Index", "Home"))
{
    <!-- ... -->
}

明確地將防偽反權杖新增至 <form> 元素,而不使用標籤協助程式搭配 HTML 協助程式@Html.AntiForgeryToken

<form asp-action="Index" asp-controller="Home" method="post">
    @Html.AntiForgeryToken()

    <!-- ... -->
</form>

在上述每個案例中,ASP.NET Core 會新增類似下列範例的隱藏表單欄位:

<input name="__RequestVerificationToken" type="hidden" value="CfDJ8NrAkS ... s2-m9Yw">

ASP.NET Core 包含三個用於防偽權杖的篩選條件

使用 AddControllers 的防偽

呼叫 AddControllers啟用防偽權杖。 必須呼叫 AddControllersWithViews 才能有內建的防偽權杖支援。

多個瀏覽器分頁和同步器權杖模式

使用同步器權杖模式時,只有最近載入的頁面包含有效的防偽權杖。 使用多個分頁可能會有問題。 例如,如果使用者開啟多個分頁:

  • 只有最近載入的分頁包含有效的防偽權杖。
  • 從先前載入的分頁提出的要求失敗,並出現錯誤:Antiforgery token validation failed. The antiforgery cookie token and request token do not match

如果造成問題,請考慮替代 CSRF 保護模式。

使用 AntiforgeryOptions 設定防偽

Program.cs 中自訂 AntiforgeryOptions

builder.Services.AddAntiforgery(options =>
{
    // Set Cookie properties using CookieBuilder properties†.
    options.FormFieldName = "AntiforgeryFieldname";
    options.HeaderName = "X-CSRF-TOKEN-HEADERNAME";
    options.SuppressXFrameOptionsHeader = false;
});

使用 CookieBuilder 類別的屬性來設定防偽 Cookie 屬性,如下表所示。

選項 描述
Cookie 決定用來建立防偽 cookie 的設定。
FormFieldName 防偽系統用來在檢視中轉譯防偽權杖的隱藏表單欄位名稱。
HeaderName 防偽系統所使用的標頭名稱。 如果 null,系統只會考慮表單資料。
SuppressXFrameOptionsHeader 指定是否要隱藏產生 X-Frame-Options 標頭。 根據預設,會產生值為「SAMEORIGIN」的標頭。 預設為 false

如需詳細資訊,請參閱CookieAuthenticationOptions

使用 IAntiforgery 產生防偽權杖

IAntiforgery 提供 API 以設定防偽功能。 您可以使用 WebApplication.Services,在 Program.cs 中要求 IAntiforgery。 下列範例會使用來自應用程式首頁的中介軟體來產生防偽權杖,並在回應中以 cookie 的形式傳送:

app.UseRouting();

app.UseAuthorization();

var antiforgery = app.Services.GetRequiredService<IAntiforgery>();

app.Use((context, next) =>
{
    var requestPath = context.Request.Path.Value;

    if (string.Equals(requestPath, "/", StringComparison.OrdinalIgnoreCase)
        || string.Equals(requestPath, "/index.html", StringComparison.OrdinalIgnoreCase))
    {
        var tokenSet = antiforgery.GetAndStoreTokens(context);
        context.Response.Cookies.Append("XSRF-TOKEN", tokenSet.RequestToken!,
            new CookieOptions { HttpOnly = false });
    }

    return next(context);
});

上述範例會設定名為 XSRF-TOKEN 的 cookie。 用戶端可以讀取此 cookie,並以附加至 AJAX 要求的標頭的形式提供其值。 例如,Angular 包含預設會讀取 cookie (名為 XSRF-TOKEN) 的內建 XSRF 保護

需要防偽驗證

ValidateAntiForgeryToken 動作篩選條件可以套用至個別動作、控制器或全域。 除非要求包含有效的防偽權杖,否則對套用此篩選動作的要求會遭到封鎖:

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Index()
{
    // ...

    return RedirectToAction();
}

ValidateAntiForgeryToken 屬性處理對其所標記動作方法的要求時需要權杖,這些要求包括 HTTP GET 要求。 如果應用程式控制器套用了 ValidateAntiForgeryToken 屬性,則可以使用 IgnoreAntiforgeryToken 屬性將其覆寫。

僅針對不安全的 HTTP 方法自動驗證防偽權杖

您可以使用 AutoValidateAntiforgeryToken 屬性,而非廣泛套用 ValidateAntiForgeryToken 屬性,然後用 IgnoreAntiforgeryToken 屬性將其覆寫。 此屬性的運作方式與 ValidateAntiForgeryToken 屬性相同,不同之處在於它在處理使用下列 HTTP 方法提出的要求時不需要權杖:

  • GET
  • HEAD
  • OPTIONS
  • TRACE

我們建議在非 API 案例中廣泛使用 AutoValidateAntiforgeryToken 。 此屬性可確保 POST 動作預設受到保護。 除非將 ValidateAntiForgeryToken 套用至個別動作方法,否則替代方法預設會忽略防偽權杖。 在此案例中,POST 動作方法可能會因錯誤而不受保護,讓應用程式容易受到 CSRF 攻擊。 所有 POST 都應該傳送防偽權杖。

API 沒有傳送非 cookie 權杖部分的自動機制。 實作可能取決於用戶端程式代碼實作。 以下顯示一些範例:

類別層級範例:

[AutoValidateAntiforgeryToken]
public class HomeController : Controller

全域範例:

builder.Services.AddControllersWithViews(options =>
{
    options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
});

覆寫全域或控制器防偽屬性

IgnoreAntiforgeryToken 篩選條件可用來消除指定動作的防偽權杖需求 (或控制器)。 套用時,此篩選條件會覆寫在較高層級指定的 ValidateAntiForgeryTokenAutoValidateAntiforgeryToken 篩選條件 (全域或控制器上)。

[IgnoreAntiforgeryToken]
public IActionResult IndexOverride()
{
    // ...

    return RedirectToAction();
}

驗證之後重新整理權杖

將使用者重新導向至檢視或 Razor 頁面頁面,而讓使用者通過驗證之後,應該重新整理權杖。

JavaScript、AJAX 和 SPA

在傳統 HTML 型應用程式中,防偽權杖會使用隱藏的表單欄位傳遞至伺服器。 在以新式 JavaScript 為基礎的應用程式和 SPA 中,會以程式設計方式提出許多要求。 這些 AJAX 要求可能會使用其他技術 (例如要求標頭或 cookie) 來傳送權杖。

如果使用 cookie 來儲存驗證權杖,並在伺服器上驗證 API 要求,則 CSRF 是潛在的問題。 如果使用本機儲存體來儲存權杖,則可以減緩 CSRF 弱點,因為本機儲存體的值不會隨著每個要求自動傳送到伺服器。 建議使用本機儲存體將防偽權杖儲存在用戶端上,並以要求標頭的形式傳送權杖。

JavaScript

使用 JavaScript 搭配檢視時,可以使用檢視內的服務來建立權杖。 將 IAntiforgery 服務插入檢視中,並呼叫 GetAndStoreTokens

@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Antiforgery

@{
    ViewData["Title"] = "JavaScript";

    var requestToken = Antiforgery.GetAndStoreTokens(Context).RequestToken;
}

<input id="RequestVerificationToken" type="hidden" value="@requestToken" />

<button id="button" class="btn btn-primary">Submit with Token</button>
<div id="result" class="mt-2"></div>

@section Scripts {
<script>
    document.addEventListener("DOMContentLoaded", () => {
        const resultElement = document.getElementById("result");

        document.getElementById("button").addEventListener("click", async () => {

            const response = await fetch("@Url.Action("FetchEndpoint")", {
                method: "POST",
                headers: {
                    RequestVerificationToken:
                        document.getElementById("RequestVerificationToken").value
                }
            });

            if (response.ok) {
                resultElement.innerText = await response.text();
            } else {
                resultElement.innerText = `Request Failed: ${response.status}`
            }
        });
    });
</script>
}

上述範例會使用 JavaScript 讀取 AJAX POST 標頭的隱藏欄位值。

這種方法不需要直接從伺服器處理設定 cookie,或從用戶端讀取它們。 不過,無法插入 IAntiforgery 服務時,JavaScript 也可以存取 cookie 中的權杖,從伺服器的額外要求取得 (通常是 same-origin),並使用 cookie 的內容來建立具有權杖值的標頭。

假設指令碼會在名為 X-XSRF-TOKEN 的要求標頭中傳送權杖,請設定防偽服務來尋找 X-XSRF-TOKEN 標頭:

builder.Services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN");

下列範例會新增受保護的端點,該端點會將要求權杖寫入 JavaScript 可讀取的 cookie:

app.UseAuthorization();
app.MapGet("antiforgery/token", (IAntiforgery forgeryService, HttpContext context) =>
{
    var tokens = forgeryService.GetAndStoreTokens(context);
    context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken!,
            new CookieOptions { HttpOnly = false });

    return Results.Ok();
}).RequireAuthorization();

下列範例會使用 JavaScript 提出 AJAX 要求,以取得權杖,並使用適當的標頭提出另一個要求:

var response = await fetch("/antiforgery/token", {
    method: "GET",
    headers: { "Authorization": authorizationToken }
});

if (response.ok) {
    // https://developer.mozilla.org/docs/web/api/document/cookie
    const xsrfToken = document.cookie
        .split("; ")
        .find(row => row.startsWith("XSRF-TOKEN="))
        .split("=")[1];

    response = await fetch("/JavaScript/FetchEndpoint", {
        method: "POST",
        headers: { "X-XSRF-TOKEN": xsrfToken, "Authorization": authorizationToken }
    });

    if (response.ok) {
        resultElement.innerText = await response.text();
    } else {
        resultElement.innerText = `Request Failed: ${response.status}`
    }
} else {    
    resultElement.innerText = `Request Failed: ${response.status}`
}

Windows 驗證和防偽 cookie

使用 Windows 驗證時,應用程式端點必須受到保護,以防止 CSRF 攻擊的方式,就像針對 cookie 所做的一樣。 瀏覽器會隱含地將驗證內容傳送至伺服器,因此必須保護端點免於 CSRF 攻擊。

擴充防偽

IAntiforgeryAdditionalDataProvider 類型可讓開發人員藉由往返每個權杖中的其他資料來擴充反 CSRF 系統的行為。 每次產生欄位權杖時都會呼叫 GetAdditionalData 方法,而且傳回值會內嵌在產生的權杖中。 實作者可以傳回時間戳記、nonce 或任何其他值,然後在驗證權杖時呼叫 ValidateAdditionalData 來驗證此資料。 用戶端的使用者名稱已經內嵌在產生的權杖中,因此不需要包含這項資訊。 如果權杖包含補充資料,但沒有 IAntiForgeryAdditionalDataProvider 設定,則不會驗證補充資料。

其他資源

跨網站偽造要求 (也稱為 XSRF 或 CSRF) 是針對 Web 裝載應用程式的攻擊,惡意 Web 應用程式可能會藉此影響用戶端瀏覽器與信任該瀏覽器的 Web 應用程式之間的互動。 這些攻擊是可能的,因為 web 瀏覽器會自動將某些類型的驗證權杖傳送給網站的每個要求。 這種形式的惡意探索也稱為單鍵攻擊會話控制,因為攻擊會利用使用者先前驗證的會話。

CSRF 攻擊的範例:

  1. 使用者使用表單驗證登入 www.good-banking-site.example.com 。 伺服器會驗證使用者,並發出回應,其中包含驗證 cookie。 該網站很容易受到攻擊,因為它信任自己所收到,包含有效驗證 cookie 的任何要求。

  2. 使用者造訪惡意網站 www.bad-crook-site.example.com

    惡意網站 www.bad-crook-site.example.com 包含類似下列範例的 HTML 表單:

    <h1>Congratulations! You're a Winner!</h1>
    <form action="https://good-banking-site.com/api/account" method="post">
        <input type="hidden" name="Transaction" value="withdraw" />
        <input type="hidden" name="Amount" value="1000000" />
        <input type="submit" value="Click to collect your prize!" />
    </form>
    

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

  3. 使用者選取 [提交] 按鈕。 瀏覽器提出要求,並自動包含所要求網域 www.good-banking-site.example.com 的驗證 cookie。

  4. 要求會以使用者的驗證內容在 www.good-banking-site.example.com 伺服器上執行,而且可以執行已驗證使用者允許執行的任何動作。

除了使用者選取按鈕以提交表單的案例之外,惡意網站還可以:

  • 執行自動提交表單的指令碼。
  • 以 AJAX 要求形式傳送表單提交。
  • 使用 CSS 隱藏表單。

除了一開始瀏覽惡意網站之外,這些替代案例並不需要來自使用者的任何動作或輸入。

使用 HTTPS 無法防止 CSRF 攻擊。 惡意網站可以傳送 https://www.good-banking-site.com/ 要求,就像傳送不安全的要求一樣容易。

某些攻擊的目標是回應 GET 要求的端點,在此情況下,可以使用影像標記來執行動作。 這種形式的攻擊在允許影像但封鎖 JavaScript 的論壇網站上很常見。 發生 GET 要求時會改變狀態 (變數或資源被變更) 的應用程式很容易遭受惡意攻擊。 會改更狀態的 GET 要求是不安全的。 最佳做法是永遠不要在發生 GET 要求時改變狀態。

CSRF 攻擊可能會針對使用 cookie 進行驗證的 Web 應用程式,因為:

  • 瀏覽器會儲存 web 應用程式發出的 cookie。
  • 儲存的 cookie 包含已驗證使用者的會話cookie。
  • 瀏覽器會將與網域關聯的所有 cookie 傳送至 Web 應用程式,不論對應用程式的要求在瀏覽器中產生的方式為何。

然而,CSRF 攻擊並不限於惡意探索 cookie。 例如,基本和摘要式驗證也很容易受到攻擊。 當使用者使用基本或摘要式驗證登入之後,瀏覽器會自動傳送認證,直到會話結束為止。

在此內容中,會話是指使用者經過驗證的用戶端會話。 這與伺服器端會話或 ASP.NET 核心會話中介軟體 無關。

使用者可以採取預防措施來防範 CSRF 弱點:

  • 使用完 Web 應用程式時登出。
  • 定期清除瀏覽器 cookie。

不過,CSRF 弱點基本上是 Web 應用程式,而不是使用者的問題。

驗證基本概念

Cookie 型的驗證是一種熱門的驗證形式。 權杖型驗證系統受歡迎的程度不斷增加,尤其是單頁應用程式 (SPA)。

當使用者使用其使用者名稱和密碼進行驗證時,會向他們發出權杖,其中包含可用於驗證和授權的驗證票證。 權杖會儲存為 cookie,並隨著用戶端發出的每個要求一起傳送。 Cookie 驗證中介軟體會產生和驗證此 cookie。 中介軟體會將使用者主體序列化為加密的 cookie。 在後續的要求中,中介軟體會驗證 cookie、重新建立主體,並將主體指派給 HttpContext.User 屬性。

權杖型驗證

當使用者經過驗證時,會向他們發出權杖 (不是防偽權杖)。 權杖會以宣告或將應用程式指向該程式所維護使用者狀態的參考權杖的形式包含使用者資訊。 當使用者嘗試存取需要驗證的資源時,權杖會以持有人權杖的形式傳送至具有額外授權標頭的應用程式。 此方法可讓應用程式變成無狀態。 在每個後續要求中,權杖會傳入伺服器端驗證的要求中。 此權杖不會加密;而是會被編碼。 在伺服器上,權杖會被解碼以存取其資訊。 若要在後續的要求上傳送權杖,請將權杖儲存在瀏覽器的本機儲存體中。 如果權杖儲存在瀏覽器的本機儲存體中,請勿擔心 CSRF 弱點。 當權杖儲存於 cookie 時,要小心 CSRF。 如需詳細資訊,請參閱 GitHub 問題 SPA 程式碼範例會新增兩個 cookie

裝載在一個網域的多個應用程式

共用主機環境容易受到會話劫持、登入 CSRF 和其他攻擊的影響。

雖然 example1.contoso.netexample2.contoso.net 是不同的主機,但 *.contoso.net 網域下的主機之間有隱含的信任關係。 此隱含信任關係可讓潛在的不受信任主機影響彼此的 cookie (控管 AJAX 要求的相同原始原則不一定能套用至 HTTP cookie)。

藉由不共用網域,即可防止惡意探索對裝載于相同網域上的應用程式之間的受信任 cookie 進行攻擊。 當每個應用程式裝載在其自己的網域上時,沒有任何隱含的 cookie 信任關係可供惡意探索。

ASP.NET Core 防偽組態

警告

ASP.NET Core 會使用 ASP.NET Core 資料保護來實作防偽。 資料保護堆疊必須設定為在伺服器陣列中運作。 如需詳細資訊,請參閱設定資料保護

Startup.ConfigureServices 中呼叫下列其中一個 API 時,防偽中介軟體會新增至相依性插入容器:

在 ASP.NET Core 2.0 或更新版本中,FormTagHelper 會將防偽權杖插入 HTML 表單元素中。 Razor 檔案中的下列標記會自動產生防偽權杖:

<form method="post">
    ...
</form>

同樣地,如果表單的方法不是 GET,則 IHtmlHelper.BeginForm 預設會產生防偽權杖。

<form> 標籤包含 method="post" 屬性,且下列任何一項為 true 時,就會自動產生 HTML 表單元素的防偽權杖:

  • 動作屬性是空的 (action="")。
  • 未提供動作屬性 (<form method="post">)。

可以停用自動產生 HTML 表單元素的防偽標記:

  • 使用 asp-antiforgery 屬性明確停用防偽權杖:

    <form method="post" asp-antiforgery="false">
        ...
    </form>
    
  • 使用標籤協助程式 ! 退出符號會使標記退出 :

    <!form method="post">
        ...
    </!form>
    
  • 從檢視中移除 FormTagHelper。 將下列指示詞新增至 Razor 檢視,即可從檢視中移除 FormTagHelper

    @removeTagHelper Microsoft.AspNetCore.Mvc.TagHelpers.FormTagHelper, Microsoft.AspNetCore.Mvc.TagHelpers
    

注意

Razor頁面會自動受到保護,免於 XSRF/CSRF 攻擊。 如需詳細資訊,請參閱 XSRF/CSRF 和 Razor 頁面

防禦 CSRF 攻擊最常見的方法是使用 同步器權杖模式 (STP)。 當使用者要求具有表單資料的頁面時,會使用 STP:

  1. 伺服器會將與目前使用者身分識別關聯的權杖傳送給用戶端。
  2. 用戶端會將權杖傳回伺服器以進行驗證。
  3. 如果伺服器收到不符合已驗證使用者身分識別的權杖,則會拒絕要求。

權杖是唯一且無法預測的。 權杖也可以用來確保一系列要求的適當排序 (例如,確保要求順序為:第 1 頁 > 第 2 頁 > 第 3 頁)。 ASP.NET Core MVC 和 Razor Pages 範本中的所有表單都會產生防偽權杖。 下列一對檢視範例會產生防偽權杖:

<form asp-controller="Todo" asp-action="Create" method="post">
    ...
</form>

@using (Html.BeginForm("Create", "Todo"))
{
    ...
}

明確地將防偽反權杖新增至 <form> 元素,而不使用標籤協助程式搭配 HTML 協助程式@Html.AntiForgeryToken

<form action="/" method="post">
    @Html.AntiForgeryToken()
</form>

在上述每個案例中,ASP.NET Core 會新增類似下列範例的隱藏表單欄位:

<input name="__RequestVerificationToken" type="hidden" value="CfDJ8NrAkS ... s2-m9Yw">

ASP.NET Core 包含三個用於防偽權杖的篩選條件

防偽選項

Startup.ConfigureServices 中自訂 AntiforgeryOptions

services.AddAntiforgery(options => 
{
    options.FormFieldName = "AntiforgeryFieldname";
    options.HeaderName = "X-CSRF-TOKEN-HEADERNAME";
    options.SuppressXFrameOptionsHeader = false;
});

使用 CookieBuilder 類別的屬性來設定防偽 Cookie 屬性,如下表所示。

選項 描述
Cookie 決定用來建立防偽 cookie 的設定。
FormFieldName 防偽系統用來在檢視中轉譯防偽權杖的隱藏表單欄位名稱。
HeaderName 防偽系統所使用的標頭名稱。 如果 null,系統只會考慮表單資料。
SuppressXFrameOptionsHeader 指定是否要隱藏產生 X-Frame-Options 標頭。 根據預設,會產生值為「SAMEORIGIN」的標頭。 預設為 false

如需詳細資訊,請參閱CookieAuthenticationOptions

使用 IAntiforgery 設定防偽功能

IAntiforgery 提供 API 以設定防偽功能。 您可以在Startup 類別的 Configure 方法中要求 IAntiforgery

在以下範例中:

  • 來自應用程式首頁的中介軟體可用來產生防偽權杖,並以 cookie 的形式在回應中傳送。
  • 要求權杖會以 JavaScript 可讀取的 cookie 的形式傳送,其中包含 AngularJS 一節中所述的預設 Angular 命名慣例。
public void Configure(IApplicationBuilder app, IAntiforgery antiforgery)
{
    app.Use(next => context =>
    {
        string path = context.Request.Path.Value;

        if (string.Equals(path, "/", StringComparison.OrdinalIgnoreCase) ||
            string.Equals(path, "/index.html", StringComparison.OrdinalIgnoreCase))
        {
            var tokens = antiforgery.GetAndStoreTokens(context);
            context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken, 
                new CookieOptions() { HttpOnly = false });
        }

        return next(context);
    });
}

需要防偽驗證

ValidateAntiForgeryToken 動作篩選條件可以套用至個別動作、控制器或全域。 除非要求包含有效的防偽權杖,否則對套用此篩選動作的要求會遭到封鎖:

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> RemoveLogin(RemoveLoginViewModel account)
{
    ManageMessageId? message = ManageMessageId.Error;
    var user = await GetCurrentUserAsync();

    if (user != null)
    {
        var result = 
            await _userManager.RemoveLoginAsync(
                user, account.LoginProvider, account.ProviderKey);

        if (result.Succeeded)
        {
            await _signInManager.SignInAsync(user, isPersistent: false);
            message = ManageMessageId.RemoveLoginSuccess;
        }
    }

    return RedirectToAction(nameof(ManageLogins), new { Message = message });
}

ValidateAntiForgeryToken 屬性處理對其所標記動作方法的要求時需要權杖,這些要求包括 HTTP GET 要求。 如果應用程式控制器套用了 ValidateAntiForgeryToken 屬性,則可以使用 IgnoreAntiforgeryToken 屬性將其覆寫。

注意

ASP.NET Core 不支援自動將防偽權杖新增至 GET 要求。

僅針對不安全的 HTTP 方法自動驗證防偽權杖

ASP.NET Core 應用程式不會為安全的 HTTP 方法產生防偽權杖 (GET、HEAD、OPTIONS 和 TRACE)。 您可以使用 AutoValidateAntiforgeryToken 屬性,而非廣泛套用 ValidateAntiForgeryToken 屬性,然後用 IgnoreAntiforgeryToken 屬性將其覆寫。 此屬性的運作方式與 ValidateAntiForgeryToken 屬性相同,不同之處在於它在處理使用下列 HTTP 方法提出的要求時不需要權杖:

  • GET
  • HEAD
  • OPTIONS
  • TRACE

我們建議在非 API 案例中廣泛使用 AutoValidateAntiforgeryToken 。 此屬性可確保 POST 動作預設受到保護。 除非將 ValidateAntiForgeryToken 套用至個別動作方法,否則替代方法預設會忽略防偽權杖。 在此案例中,POST 動作方法可能會因錯誤而不受保護,讓應用程式容易受到 CSRF 攻擊。 所有 POST 都應該傳送防偽權杖。

API 沒有傳送非 cookie 權杖部分的自動機制。 實作可能取決於用戶端程式代碼實作。 以下顯示一些範例:

類別層級範例:

[Authorize]
[AutoValidateAntiforgeryToken]
public class ManageController : Controller
{

全域範例:

services.AddControllersWithViews(options =>
    options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute()));

覆寫全域或控制器防偽屬性

IgnoreAntiforgeryToken 篩選條件可用來消除指定動作的防偽權杖需求 (或控制器)。 套用時,此篩選條件會覆寫在較高層級指定的 ValidateAntiForgeryTokenAutoValidateAntiforgeryToken 篩選條件 (全域或控制器上)。

[Authorize]
[AutoValidateAntiforgeryToken]
public class ManageController : Controller
{
    [HttpPost]
    [IgnoreAntiforgeryToken]
    public async Task<IActionResult> DoSomethingSafe(SomeViewModel model)
    {
        // no antiforgery token required
    }
}

驗證之後重新整理權杖

將使用者重新導向至檢視或 Razor 頁面頁面,而讓使用者通過驗證之後,應該重新整理權杖。

JavaScript、AJAX 和 SPA

在傳統 HTML 型應用程式中,防偽權杖會使用隱藏的表單欄位傳遞至伺服器。 在以新式 JavaScript 為基礎的應用程式和 SPA 中,會以程式設計方式提出許多要求。 這些 AJAX 要求可能會使用其他技術 (例如要求標頭或 cookie) 來傳送權杖。

如果使用 cookie 來儲存驗證權杖,並在伺服器上驗證 API 要求,則 CSRF 是潛在的問題。 如果使用本機儲存體來儲存權杖,則可以減緩 CSRF 弱點,因為本機儲存體的值不會隨著每個要求自動傳送到伺服器。 建議使用本機儲存體將防偽權杖儲存在用戶端上,並以要求標頭的形式傳送權杖。

JavaScript

使用 JavaScript 搭配檢視時,可以使用檢視內的服務來建立權杖。 將 IAntiforgery 服務插入檢視中,並呼叫 GetAndStoreTokens

@{
    ViewData["Title"] = "AJAX Demo";
}
@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Xsrf
@functions{
    public string GetAntiXsrfRequestToken()
    {
        return Xsrf.GetAndStoreTokens(Context).RequestToken;
    }
}

<input type="hidden" id="RequestVerificationToken" 
       name="RequestVerificationToken" value="@GetAntiXsrfRequestToken()">

<h2>@ViewData["Title"].</h2>
<h3>@ViewData["Message"]</h3>

<div class="row">
    <p><input type="button" id="antiforgery" value="Antiforgery"></p>
    <script>
        var xhttp = new XMLHttpRequest();
        xhttp.onreadystatechange = function() {
            if (xhttp.readyState == XMLHttpRequest.DONE) {
                if (xhttp.status == 200) {
                    alert(xhttp.responseText);
                } else {
                    alert('There was an error processing the AJAX request.');
                }
            }
        };

        document.addEventListener('DOMContentLoaded', function() {
            document.getElementById("antiforgery").onclick = function () {
                xhttp.open('POST', '@Url.Action("Antiforgery", "Home")', true);
                xhttp.setRequestHeader("RequestVerificationToken", 
                    document.getElementById('RequestVerificationToken').value);
                xhttp.send();
            }
        });
    </script>
</div>

這種方法不需要直接從伺服器處理設定 cookie,或從用戶端讀取它們。

上述範例會使用 JavaScript 讀取 AJAX POST 標頭的隱藏欄位值。

JavaScript 也可以存取 cookie 中的權杖,並使用 cookie 的內容來建立具有權杖值的標頭。

context.Response.Cookies.Append("CSRF-TOKEN", tokens.RequestToken, 
    new Microsoft.AspNetCore.Http.CookieOptions { HttpOnly = false });

假設指令碼會在名為 X-CSRF-TOKEN 的標頭中傳送權杖,請設定防偽服務來尋找 X-CSRF-TOKEN 標頭:

services.AddAntiforgery(options => options.HeaderName = "X-CSRF-TOKEN");

下列範例會使用 JavaScript 來提出具有適當標頭的 AJAX 要求:

function getCookie(cname) {
    var name = cname + "=";
    var decodedCookie = decodeURIComponent(document.cookie);
    var ca = decodedCookie.split(';');
    for (var i = 0; i < ca.length; i++) {
        var c = ca[i];
        while (c.charAt(0) === ' ') {
            c = c.substring(1);
        }
        if (c.indexOf(name) === 0) {
            return c.substring(name.length, c.length);
        }
    }
    return "";
}

var csrfToken = getCookie("CSRF-TOKEN");

var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function () {
    if (xhttp.readyState === XMLHttpRequest.DONE) {
        if (xhttp.status === 204) {
            alert('Todo item is created successfully.');
        } else {
            alert('There was an error processing the AJAX request.');
        }
    }
};
xhttp.open('POST', '/api/items', true);
xhttp.setRequestHeader("Content-type", "application/json");
xhttp.setRequestHeader("X-CSRF-TOKEN", csrfToken);
xhttp.send(JSON.stringify({ "name": "Learn C#" }));

AngularJS

Angular JS 會使用慣例來處理 CSRF。 如果伺服器傳送名稱為 XSRF-TOKEN 的 cookie,AngularJS$http 服務會在將要求傳送至伺服器時,將 cookie 值新增至標頭。 此程序是自動的。 用戶端不需要明確設定標頭。 標頭名稱為 X-XSRF-TOKEN。 伺服器應該偵測此標頭並驗證其內容。

若要讓 ASP.NET Core API 在應用程式啟動時使用此慣例:

  • 將您的應用程式設定為在稱為 XSRF-TOKEN 的 cookie中提供權杖。
  • 設定防偽服務來尋找名為 X-XSRF-TOKEN 的標頭,這是用來傳送 XSRF 權杖的 Angular 預設標頭名稱。
public void Configure(IApplicationBuilder app, IAntiforgery antiforgery)
{
    app.Use(next => context =>
    {
        string path = context.Request.Path.Value;

        if (
            string.Equals(path, "/", StringComparison.OrdinalIgnoreCase) ||
            string.Equals(path, "/index.html", StringComparison.OrdinalIgnoreCase))
        {
            var tokens = antiforgery.GetAndStoreTokens(context);
            context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken, 
                new CookieOptions() { HttpOnly = false });
        }

        return next(context);
    });
}

public void ConfigureServices(IServiceCollection services)
{
    services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN");
}

注意

當要求標頭和表單承載中都提供防偽權杖時,只會驗證標頭中的權杖。

Windows 驗證和防偽 cookie

使用 Windows 驗證時,應用程式端點必須受到保護,以防止 CSRF 攻擊的方式,就像針對 cookie 所做的一樣。 瀏覽器會隱含地將驗證內容傳送至伺服器,因此必須保護端點免於 CSRF 攻擊。

擴充防偽

IAntiforgeryAdditionalDataProvider 類型可讓開發人員藉由往返每個權杖中的其他資料來擴充反 CSRF 系統的行為。 每次產生欄位權杖時都會呼叫 GetAdditionalData 方法,而且傳回值會內嵌在產生的權杖中。 實作者可以傳回時間戳記、nonce 或任何其他值,然後在驗證權杖時呼叫 ValidateAdditionalData 來驗證此資料。 用戶端的使用者名稱已經內嵌在產生的權杖中,因此不需要包含這項資訊。 如果權杖包含補充資料,但沒有 IAntiForgeryAdditionalDataProvider 設定,則不會驗證補充資料。

其他資源