共用方式為


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

作者如下:Fiyaz Bin HasanRick Anderson

跨網站請求偽造是一種針對網頁託管應用程式的攻擊,惡意的網頁應用程式可能會從而影響用戶端瀏覽器與信任該瀏覽器的應用程式之間的互動。 這些攻擊是可能的,因為網頁瀏覽器會自動將某些類型的驗證權杖傳送給每次對網站的請求。 這種形式的漏洞利用也稱為一鍵攻擊會話駭客,因為攻擊會利用使用者先前驗證的會話。 跨站請求偽造也被稱為 XSRF 或 CSRF。

CSRF 攻擊的範例:

  1. 使用者使用表單驗證登入 www.good-banking-site.example.com 。 伺服器會驗證使用者,並發出回應,其中包含驗證 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://www.good-banking-site.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 to collect your prize!" />
    </form>
    

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

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

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

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

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

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

使用 HTTPS 無法防止 CSRF 攻擊。 惡意網站可以像傳送不安全請求一樣輕鬆地傳送 https://www.good-banking-site.example.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 資料保護來實作防偽。 資料保護堆疊必須設定為在伺服器陣列中運作。 如需詳細資訊,請參閱設定資料保護

中呼叫下列其中一個 API 時,防偽中介軟體會新增至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 屬性明確停用防偽 Token:

    <form method="post" asp-antiforgery="false">
        <!-- ... -->
    </form>
    
  • 將表單元素從標籤協助程式中退出,使用標籤協助程式的! 退出符號

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

    @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 才能有內建的防偽 Token 支援。

多個瀏覽器分頁和同步器令牌模式

不支援以不同使用者身分登入或以匿名身分登入的多個索引標籤。

使用 AntiforgeryOptions 設定防偽

在應用程式的AntiforgeryOptions檔案中自訂Program

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

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

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

部分瀏覽器不允許不安全的端點設定具有「安全」旗標的 Cookie,或覆寫已設定「安全」旗標的 Cookie (如需詳細資訊,請參閱取代從 非安全來源修改「安全」Cookie)。 因為在應用程式中,同時使用安全和不安全的端點是常見情形,因此 ASP.NET Core 會透過將 cookie 的 cookie 設定為 SecurePolicy,來放寬如防偽等某些 Cookie 的安全性限制。 即使惡意使用者竊取了防偽造cookie,他們還必須竊取通常透過表單欄位(較常見)或獨立的請求標頭(較不常見)傳送的防偽造令牌以及驗證cookie。 與身份驗證或授權相關的 Cookie 使用比 更 CookieSecurePolicy.None強的策略。

或者,您可以使用安全通訊端層 (SSL) 在非開發環境中保護防偽cookie,僅透過 HTTPS,並在應用程式檔案AntiforgeryOptions.Cookie中使用下列Program屬性設定:

if (!builder.Environment.IsDevelopment())
{
    builder.Services.AddAntiforgery(o =>
    {
        o.Cookie.SecurePolicy = CookieSecurePolicy.Always;
    });
}

如需詳細資訊,請參閱CookieAuthenticationOptions

使用 IAntiforgery 產生防偽權杖

IAntiforgery 提供 API 以設定防偽功能。 在 IAntiforgery 中,您可以使用 Program.cs 来请求 WebApplication.Services。 下列範例會使用應用程式首頁的中間件來產生防偽令牌,並在回應中以 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);
});

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

需要防偽驗證

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

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

    return RedirectToAction();
}

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

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

與其廣泛地套用 ValidateAntiForgeryToken 屬性,然後再用 IgnoreAntiforgeryToken 屬性來覆寫,不如使用 AutoValidateAntiforgeryToken 屬性。 此屬性的運作原理與 ValidateAntiForgeryToken 屬性相同,差別在於此屬性會在處理使用下列 HTTP 方法提出的要求時不需要權杖:

  • GET
  • 選項
  • 追蹤

我們建議在非 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 Pages 頁面,並重新整理標記。

JavaScript、AJAX 和 SPA

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

如果使用 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 中的 Token。

  • 伺服器額外要求中的存取權杖通常是 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 的防偽措施

呼叫 AddAntiforgeryUseAntiforgery(IApplicationBuilder) 以在 DI 中註冊防偽服務。 防偽代幣可用來緩解跨網站請求偽造攻擊

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 方法
  • 要求與有效的端點相關聯。

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

預設情況下,接受表單資料的 Minimal 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 端點產生的表單 / 會成功,因為防偽權杖有效。
  • /todo 產生的表單 /SkipToken 則失敗了,因為不包含防偽資訊。
  • /todo2 端點產生的表單/DisableAntiforgery 會成功,因為不需要防偽權杖。
app.MapPost("/todo", ([FromForm] Todo todo) => Results.Ok(todo));

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

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

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

Windows 驗證和防偽機制的 Cookie

使用 Windows 驗證時,應用程式端點必須像保護 Cookie 一樣防範 CSRF 攻擊。 瀏覽器會隱含地將驗證內容傳送至伺服器,而且必須保護端點免於 CSRF 攻擊。

擴展防偽

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

其他資源

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

CSRF 攻擊的範例:

  1. 使用者使用表單驗證登入 www.good-banking-site.example.com 。 伺服器會驗證使用者,並發出回應,其中包含驗證 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://www.good-banking-site.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 to collect your prize!" />
    </form>
    

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

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

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

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

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

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

使用 HTTPS 無法防止 CSRF 攻擊。 惡意網站可以像傳送不安全請求一樣輕鬆地傳送 https://www.good-banking-site.example.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 資料保護來實作防偽。 資料保護堆疊必須設定為在伺服器陣列中運作。 如需詳細資訊,請參閱設定資料保護

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

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

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

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

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

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

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

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

    <form method="post" asp-antiforgery="false">
        <!-- ... -->
    </form>
    
  • 將表單元素從標籤協助程式中退出,使用標籤協助程式的! 退出符號

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

    @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 才能有內建的防偽 Token 支援。

多個瀏覽器分頁和同步器令牌模式

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

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

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

使用 AntiforgeryOptions 設定防偽

在應用程式的AntiforgeryOptions檔案中自訂Program

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

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

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

部分瀏覽器不允許不安全的端點設定具有「安全」旗標的 Cookie,或覆寫已設定「安全」旗標的 Cookie (如需詳細資訊,請參閱取代從 非安全來源修改「安全」Cookie)。 因為在應用程式中,同時使用安全和不安全的端點是常見情形,因此 ASP.NET Core 會透過將 cookie 的 cookie 設定為 SecurePolicy,來放寬如防偽等某些 Cookie 的安全性限制。 即使惡意使用者竊取了防偽造cookie,他們還必須竊取通常透過表單欄位(較常見)或獨立的請求標頭(較不常見)傳送的防偽造令牌以及驗證cookie。 與身份驗證或授權相關的 Cookie 使用比 更 CookieSecurePolicy.None強的策略。

或者,您可以使用安全通訊端層 (SSL) 在非開發環境中保護防偽cookie,僅透過 HTTPS,並在應用程式檔案AntiforgeryOptions.Cookie中使用下列Program屬性設定:

if (!builder.Environment.IsDevelopment())
{
    builder.Services.AddAntiforgery(o =>
    {
        o.Cookie.SecurePolicy = CookieSecurePolicy.Always;
    });
}

如需詳細資訊,請參閱CookieAuthenticationOptions

使用 IAntiforgery 產生防偽權杖

IAntiforgery 提供 API 以設定防偽功能。 在 IAntiforgery 中,您可以使用 Program.cs 来请求 WebApplication.Services。 下列範例會使用應用程式首頁的中間件來產生防偽令牌,並在回應中以 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);
});

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

需要防偽驗證

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

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

    return RedirectToAction();
}

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

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

與其廣泛地套用 ValidateAntiForgeryToken 屬性,然後再用 IgnoreAntiforgeryToken 屬性來覆寫,不如使用 AutoValidateAntiforgeryToken 屬性。 此屬性的運作原理與 ValidateAntiForgeryToken 屬性相同,差別在於此屬性會在處理使用下列 HTTP 方法提出的要求時不需要權杖:

  • GET
  • 選項
  • 追蹤

我們建議在非 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 Pages 頁面,並重新整理標記。

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 中的 Token。

  • 伺服器額外要求中的存取權杖通常是 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 驗證時,應用程式端點必須像保護 Cookie 一樣防範 CSRF 攻擊。 瀏覽器會隱含地將驗證內容傳送至伺服器,而且必須保護端點免於 CSRF 攻擊。

擴展防偽

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

其他資源

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

CSRF 攻擊的範例:

  1. 使用者使用表單驗證登入 www.good-banking-site.example.com 。 伺服器會驗證使用者,並發出回應,其中包含驗證 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://www.good-banking-site.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 to collect your prize!" />
    </form>
    

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

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

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

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

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

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

使用 HTTPS 無法防止 CSRF 攻擊。 惡意網站可以像傳送不安全請求一樣輕鬆地傳送 https://www.good-banking-site.example.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 資料保護來實作防偽。 資料保護堆疊必須設定為在伺服器陣列中運作。 如需詳細資訊,請參閱設定資料保護

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

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

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

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

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

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

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

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

    <form method="post" asp-antiforgery="false">
        <!-- ... -->
    </form>
    
  • 將表單元素從標籤協助程式中退出,使用標籤協助程式的! 退出符號

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

    @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 才能有內建的防偽 Token 支援。

多個瀏覽器分頁和同步器令牌模式

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

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

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

使用 AntiforgeryOptions 設定防偽

在應用程式的AntiforgeryOptions檔案中自訂Program

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

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

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

部分瀏覽器不允許不安全的端點設定具有「安全」旗標的 Cookie,或覆寫已設定「安全」旗標的 Cookie (如需詳細資訊,請參閱取代從 非安全來源修改「安全」Cookie)。 因為在應用程式中,同時使用安全和不安全的端點是常見情形,因此 ASP.NET Core 會透過將 cookie 的 cookie 設定為 SecurePolicy,來放寬如防偽等某些 Cookie 的安全性限制。 即使惡意使用者竊取了防偽造cookie,他們還必須竊取通常透過表單欄位(較常見)或獨立的請求標頭(較不常見)傳送的防偽造令牌以及驗證cookie。 與身份驗證或授權相關的 Cookie 使用比 更 CookieSecurePolicy.None強的策略。

或者,您可以使用安全通訊端層 (SSL) 在非開發環境中保護防偽cookie,僅透過 HTTPS,並在應用程式檔案AntiforgeryOptions.Cookie中使用下列Program屬性設定:

if (!builder.Environment.IsDevelopment())
{
    builder.Services.AddAntiforgery(o =>
    {
        o.Cookie.SecurePolicy = CookieSecurePolicy.Always;
    });
}

如需詳細資訊,請參閱CookieAuthenticationOptions

使用 IAntiforgery 產生防偽權杖

IAntiforgery 提供 API 以設定防偽功能。 在 IAntiforgery 中,您可以使用 Program.cs 来请求 WebApplication.Services。 下列範例會使用應用程式首頁的中間件來產生防偽令牌,並在回應中以 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);
});

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

需要防偽驗證

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

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

    return RedirectToAction();
}

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

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

與其廣泛地套用 ValidateAntiForgeryToken 屬性,然後再用 IgnoreAntiforgeryToken 屬性來覆寫,不如使用 AutoValidateAntiforgeryToken 屬性。 此屬性的運作原理與 ValidateAntiForgeryToken 屬性相同,差別在於此屬性會在處理使用下列 HTTP 方法提出的要求時不需要權杖:

  • GET
  • 選項
  • 追蹤

我們建議在非 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 Pages 頁面,並重新整理標記。

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 也可以透過對伺服器發出的額外請求(通常是 same-origin)取得的資料,來從 Cookie 中存取權杖,並使用 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 驗證時,應用程式端點必須像保護 Cookie 一樣防範 CSRF 攻擊。 瀏覽器會隱含地將驗證內容傳送至伺服器,因此必須保護端點免於 CSRF 攻擊。

擴展防偽

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

其他資源

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

CSRF 攻擊的範例:

  1. 使用者使用表單驗證登入 www.good-banking-site.example.com 。 伺服器會驗證使用者,並發出回應,其中包含驗證 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://www.good-banking-site.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 to collect your prize!" />
    </form>
    

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

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

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

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

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

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

使用 HTTPS 無法防止 CSRF 攻擊。 惡意網站可以像傳送不安全請求一樣輕鬆地傳送 https://www.good-banking-site.example.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 資料保護來實作防偽。 資料保護堆疊必須設定為在伺服器陣列中運作。 如需詳細資訊,請參閱設定資料保護

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

在 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 屬性明確停用防偽 Token:

    <form method="post" asp-antiforgery="false">
        ...
    </form>
    
  • 將表單元素從標籤協助程式中退出,使用標籤協助程式的! 退出符號

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

    @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 包含三個用於防偽權杖的篩選器

防偽選項

自訂 AntiforgeryOptionsStartup.ConfigureServices 中。

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

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

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

部分瀏覽器不允許不安全的端點設定具有「安全」旗標的 Cookie,或覆寫已設定「安全」旗標的 Cookie (如需詳細資訊,請參閱取代從 非安全來源修改「安全」Cookie)。 因為在應用程式中,同時使用安全和不安全的端點是常見情形,因此 ASP.NET Core 會透過將 cookie 的 cookie 設定為 SecurePolicy,來放寬如防偽等某些 Cookie 的安全性限制。 即使惡意使用者竊取了防偽造cookie,他們還必須竊取通常透過表單欄位(較常見)或獨立的請求標頭(較不常見)傳送的防偽造令牌以及驗證cookie。 與身份驗證或授權相關的 Cookie 使用比 更 CookieSecurePolicy.None強的策略。

或者,您可以使用安全通訊端層 (SSL) 在非開發環境中保護防偽cookie,僅透過 HTTPS 進行,並在應用程式類別AntiforgeryOptions.Cookie中使用下列Startup屬性設定:

public class Startup
{
    public Startup(IConfiguration configuration, IHostEnvironment environment)
    {
        Configuration = configuration;
        Environment = environment;
    }

    public IConfiguration Configuration { get; }
    public IHostEnvironment Environment { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        // Other services are registered here

        if (!Environment.IsDevelopment())
        {
            services.AddAntiforgery(o =>
            {
                o.Cookie.SecurePolicy = CookieSecurePolicy.Always;
            });
        }
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        // Request processing pipeline
    }
}

如需詳細資訊,請參閱CookieAuthenticationOptions

使用 IAntiforgery 設定防偽功能

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

在以下範例中:

  • 應用程式首頁的中間件被用來產生防偽令牌,並以 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)。 與其廣泛地套用 ValidateAntiForgeryToken 屬性,然後再用 IgnoreAntiforgeryToken 屬性來覆寫,不如使用 AutoValidateAntiforgeryToken 屬性。 此屬性的運作原理與 ValidateAntiForgeryToken 屬性相同,差別在於此屬性會在處理使用下列 HTTP 方法提出的要求時不需要權杖:

  • GET
  • 選項
  • 追蹤

我們建議在非 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 Pages 頁面,並重新整理標記。

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

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

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

  • 將您的應用程式設定為在稱為 cookie 的 XSRF-TOKEN中提供權杖。
  • 設定防偽服務來尋找名為 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 驗證時,應用程式端點必須像保護 Cookie 一樣防範 CSRF 攻擊。 瀏覽器會隱含地將驗證內容傳送至伺服器,因此必須保護端點免於 CSRF 攻擊。

擴展防偽

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

其他資源