防止 ASP.NET 核心中的跨網站偽造要求 (XSRF/CSRF) 攻擊
作者如下:Fiyaz Bin Hasan 和 Rick Anderson
跨網站請求偽造是一種針對網頁託管應用程式的攻擊,惡意的網頁應用程式可能會從而影響用戶端瀏覽器與信任該瀏覽器的應用程式之間的互動。 這些攻擊是可能的,因為網頁瀏覽器會自動將某些類型的驗證權杖傳送給每次對網站的請求。 這種形式的漏洞利用也稱為一鍵攻擊或會話駭客,因為攻擊會利用使用者先前驗證的會話。 跨站請求偽造也被稱為 XSRF 或 CSRF。
CSRF 攻擊的範例:
使用者使用表單驗證登入
www.good-banking-site.example.com
。 伺服器會驗證使用者,並發出回應,其中包含驗證 cookie。 該網站很容易受到攻擊,因為它信任所有包含有效驗證的請求。使用者造訪惡意網站
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 的「跨網站」部分。使用者選取 [提交] 按鈕。 瀏覽器發送請求,並自動包含所需網域 cookie 的驗證
www.good-banking-site.example.com
。要求會以使用者的驗證內容在
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。 在後續的要求中,中介軟體會驗證 cookie、重新建立主體,並將主體指派給 HttpContext.User 屬性。
基於令牌的驗證
當使用者經過驗證時,會向他們發出權杖 (不是防偽權杖)。 權杖包含使用者資訊,可以是宣告的形式,也可以是指向應用程式中儲存使用者狀態的參考權杖。 當使用者嘗試存取需要驗證的資源時,權杖會以持有人權杖的形式傳送至具有額外授權標頭的應用程式。 此方法可讓應用程式變成無狀態。 在每次後續請求中,伺服器端驗證的請求會包含令牌。 此令牌不是加密的,而是編碼的。 在伺服器上,權杖會被解碼以存取其資訊。 若要在後續的要求上傳送權杖,請將權杖儲存在瀏覽器的本機儲存體中。 將權杖放在瀏覽器本機儲存體中,並加以擷取,並將其視為持有人權杖使用,可保護 CSRF 攻擊。 不過,如果應用程式因 XSS 或遭入侵的外部 JavaScript 檔案而遭受腳本注入攻擊,網絡攻擊者可以從本地儲存擷取任何值,並將其發送到自身。 ASP.NET Core 預設會將變數的所有伺服器端輸出編碼,以降低 XSS 的風險。 如果您使用 Html.Raw 或具有不受信任輸入的自訂程式碼來覆寫此行為 ,可能會增加 XSS 的風險。
如果令牌存儲在瀏覽器的本機存儲空間中,無需擔心 CSRF 弱點。 當保存權杖於cookie時,需注意 CSRF 攻擊。 如需詳細資訊,請參閱 GitHub 問題 SPA 程式碼範例會新增兩個 Cookie。
裝載在一個網域的多個應用程式
共用主機環境容易受到會話劫持、登入 CSRF 及其他攻擊的影響。
雖然 example1.contoso.net
和 example2.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 頁)。 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.cs
中。
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 。 |
如需詳細資訊,請參閱CookieAuthenticationOptions。
使用 IAntiforgery
產生防偽權杖
IAntiforgery 提供 API 以設定防偽功能。 在 Program.cs
中,您可以使用 IAntiforgery
来请求 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);
});
名為 XSRF-TOKEN
的 cookie 已在上述範例中設定。 用戶端可以讀取此 cookie,並以附加至 AJAX 要求的標頭的形式提供其值。 例如,Angular 包含內建的 XSRF 保護,預設會讀取名為 cookie 的 XSRF-TOKEN
。
需要防偽驗證
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 篩選條件可用來消除指定動作的防偽權杖需求 (或控制器)。 套用此篩選器時,它會覆寫在較高層級(全域或控制器)設定的 ValidateAntiForgeryToken
和 AutoValidateAntiforgeryToken
篩選器。
[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 的防偽措施
呼叫 AddAntiforgery 和 UseAntiforgery(IApplicationBuilder) 以在 DI 中註冊防偽服務。 防偽代幣可用來緩解跨網站請求偽造攻擊。
var builder = WebApplication.CreateBuilder();
builder.Services.AddAntiforgery();
var app = builder.Build();
app.UseAntiforgery();
app.MapGet("/", () => "Hello World!");
app.Run();
防偽中介軟體:
- 不是會中斷 其餘请求管線的執行嗎?
- 將當前請求中的 IAntiforgeryValidationFeature 設置為 HttpContext.Features。
只有在下列狀況下,才會驗證防偽權杖:
- 端點包含實作 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
端點產生的表單/
會成功,因為防偽權杖有效。 -
/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 攻擊的範例:
使用者使用表單驗證登入
www.good-banking-site.example.com
。 伺服器會驗證使用者,並發出回應,其中包含驗證 cookie。 該網站容易受到攻擊,因為它信任任何包含有效認證cookie的請求。使用者造訪惡意網站
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 的「跨網站」部分。使用者選取 [提交] 按鈕。 瀏覽器提出請求並自動包含針對所要求的網域驗證 cookie
www.good-banking-site.example.com
。要求會以使用者的驗證內容在
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。 在後續的要求中,中介軟體會驗證 cookie、重新建立主體,並將主體指派給 HttpContext.User 屬性。
令牌驗證
當使用者經過驗證時,會向他們發出權杖 (不是防偽權杖)。 權杖會以宣告的形式包含使用者資訊,或者作為一個參考權杖,該參考權杖將應用程式指向應用程式中維護的使用者狀態。 當使用者嘗試存取需要驗證的資源時,權杖會以持有人權杖的形式傳送至具有額外授權標頭的應用程式。 此方法可讓應用程式變成無狀態。 在每個後續請求中,權杖會在請求中被傳送到伺服器端做驗證。 此代幣不是加密的,而是編碼的。 在伺服器上,權杖會被解碼以存取其資訊。 若要在後續的要求上傳送權杖,請將權杖儲存在瀏覽器的本機儲存體中。 將權杖放在瀏覽器本機儲存體中,並加以擷取,並將其視為持有人權杖使用,可保護 CSRF 攻擊。 然而,如果應用程式易於透過 XSS 或遭入侵的外部 JavaScript 檔案進行指令碼注入,網路攻擊者可以提取本機儲存體中的任何值並將其發送給自己。 ASP.NET Core 預設會將變數的所有伺服器端輸出編碼,以降低 XSS 的風險。 如果您使用 Html.Raw 或具有不受信任輸入的自訂程式碼來覆蓋此行為,可能會增加 XSS 的風險。
如果令牌儲存在瀏覽器的本機儲存體中,請勿擔心 CSRF 弱點。 當權杖儲存於 cookie 時,要小心 CSRF。 如需詳細資訊,請參閱 GitHub 問題 SPA 程式碼範例會新增兩個 Cookie。
裝載在一個網域的多個應用程式
共用主機環境容易受到會話劫持、登入 CSRF 和其他攻擊的影響。
雖然 example1.contoso.net
和 example2.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
屬性明確地停用防偽憑證:<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 頁)。 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
設定防偽
在 AntiforgeryOptions 中自訂 Program.cs
:
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 。 |
如需詳細資訊,請參閱CookieAuthenticationOptions。
使用 IAntiforgery
產生防偽權杖
IAntiforgery 提供 API 以設定防偽功能。 可以在 Program.cs
中使用 WebApplication.Services 請求 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);
});
上述範例會設定名為 cookie 的 XSRF-TOKEN
。 用戶端可以讀取此 cookie,並以附加至 AJAX 要求的標頭的形式提供其值。 例如,Angular 包含內建的 XSRF 保護功能,該功能預設會讀取一個名為 cookie 的XSRF-TOKEN
。
需要防偽驗證
ValidateAntiForgeryToken 動作篩選條件可以套用至個別動作、控制器或全域。 除非要求包含有效的防偽權杖,否則對套用此篩選動作的要求會遭到封鎖:
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Index()
{
// ...
return RedirectToAction();
}
ValidateAntiForgeryToken
屬性處理對其所標記動作方法的要求時需要權杖,這些要求包括 HTTP GET 要求。 如果應用程式控制器套用了 ValidateAntiForgeryToken
屬性,則可以使用 IgnoreAntiforgeryToken
屬性將其覆寫。
僅針對不安全的 HTTP 方法自動驗證防偽權杖
可以使用 AutoValidateAntiforgeryToken 屬性,而不是廣泛套用 ValidateAntiForgeryToken
屬性後再用 IgnoreAntiforgeryToken
屬性覆寫。 此屬性與 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 篩選條件可用來免除某一動作(或控制器)對防偽權杖的要求。 套用時,此篩選條件會覆寫在更高層級上指定的 ValidateAntiForgeryToken
和 AutoValidateAntiforgeryToken
篩選條件,無論是在全域或是控制器上。
[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}`
}
注意
當在請求標頭和表單負載中都提供防偽權杖時,僅驗證標頭中的權杖。
使用Minimal API的防偽
Minimal APIs
不支援使用包含的篩選條件 (ValidateAntiForgeryToken
、AutoValidateAntiforgeryToken
、IgnoreAntiforgeryToken
),不過 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 應用程式之間的互動。 這些攻擊是可能的,因為網頁瀏覽器會自動將某些類型的驗證權杖傳送至網站的每個請求。 這種形式的利用攻擊也稱為一鍵攻擊或會話劫持,因為攻擊會利用使用者先前已驗證的會話。
CSRF 攻擊的範例:
使用者使用表單驗證登入
www.good-banking-site.example.com
。 伺服器會驗證使用者,並發出回應,其中包含驗證 cookie。 該網站很容易受到攻擊,因為它信任任何包含有效驗證的接收到的請求。使用者造訪惡意網站
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 的「跨網站」部分。使用者選取 [提交] 按鈕。 瀏覽器發送請求,並自動附加針對所請求網域 cookie 的驗證資訊
www.good-banking-site.example.com
。要求會以使用者的驗證內容在
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。 在後續的要求中,中介軟體會驗證 cookie、重新建立主體,並將主體指派給 HttpContext.User 屬性。
令牌型驗證
當使用者經過驗證時,會向他們發出權杖 (不是防偽權杖)。 權杖會以宣告或將應用程式指向該程式所維護使用者狀態的參考權杖的形式包含使用者資訊。 當使用者嘗試存取需要驗證的資源時,權杖會以持有人權杖的形式傳送至具有額外授權標頭的應用程式。 此方法可讓應用程式變成無狀態。 在每個後續請求中,權杖會被傳送至伺服器端進行驗證。 此令牌不是加密的,而是編碼的。 在伺服器上,憑證會被解碼以取得其資訊。 若要在後續的要求上傳送權杖,請將權杖儲存在瀏覽器的本機儲存體中。 如果令牌儲存在瀏覽器的本機儲存體中,請勿擔心 CSRF 弱點。 在將權杖存放於 cookie 時,須留意 CSRF 的問題。 如需詳細資訊,請參閱 GitHub 問題 SPA 程式碼範例會新增兩個 Cookie。
裝載在一個網域的多個應用程式
共用主機環境容易受到會話劫持、登入 CSRF 和其他攻擊的影響。
雖然 example1.contoso.net
和 example2.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
。 將下列指示詞新增至FormTagHelper
檢視,即可從檢視中移除 Razor:@removeTagHelper Microsoft.AspNetCore.Mvc.TagHelpers.FormTagHelper, Microsoft.AspNetCore.Mvc.TagHelpers
注意
Razor頁面會自動受到保護,免於 XSRF/CSRF 攻擊。 如需詳細資訊,請參閱 XSRF/CSRF 和 Razor 頁面。
防禦 CSRF 攻擊最常見的方法是使用 同步器權杖模式 (STP)。 當使用者要求具有表單資料的頁面時,會使用 STP:
- 伺服器會將與目前使用者身分識別相關聯的令牌傳送給用戶端。
- 用戶端會將權杖傳回伺服器以進行驗證。
- 如果伺服器收到不符合已驗證使用者身分識別的令牌,則會拒絕要求。
代幣是唯一且無法預測的。 權杖也可以用來確保一系列要求的適當排序 (例如,確保要求順序為:第 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;
});
使用 Cookie
類別的屬性來設定防偽 CookieBuilder 屬性,如下表所示。
選項 | 描述 |
---|---|
Cookie | 決定用於創建防偽 Cookie 的設定。 |
FormFieldName | 防偽系統用來在檢視中轉譯防偽權杖的隱藏表單欄位名稱。 |
HeaderName | 防偽系統所使用的標頭名稱。 如果 null ,系統只會考慮表單資料。 |
SuppressXFrameOptionsHeader | 指定是否要抑制產生 X-Frame-Options 標頭。 根據預設,會產生值為「SAMEORIGIN」的標頭。 預設為 false 。 |
如需詳細資訊,請參閱CookieAuthenticationOptions。
使用 IAntiforgery
產生防偽權杖
IAntiforgery 提供 API 以設定防偽功能。 可以在 Program.cs
使用 WebApplication.Services 來請求 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);
});
上述範例會設定名為 cookie 的 XSRF-TOKEN
。 用戶端可以讀取此 cookie,並以附加至 AJAX 要求的標頭的形式提供其值。 例如,Angular 包含內建的 XSRF 保護功能,預設會讀取名為cookie的。
需要防偽驗證
ValidateAntiForgeryToken 動作篩選條件可以套用至個別動作、控制器或全域。 除非請求包含有效的防偽權杖,否則對套用此篩選器的動作的請求會被封鎖。
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Index()
{
// ...
return RedirectToAction();
}
ValidateAntiForgeryToken
屬性在處理對其所標記的動作方法的請求時需要權杖,包括 HTTP GET 請求。 如果應用程式控制器套用了 ValidateAntiForgeryToken
屬性,則可以使用 IgnoreAntiforgeryToken
屬性將其覆寫。
僅針對不安全的 HTTP 方法自動驗證防偽權杖
與其廣泛套用 ValidateAntiForgeryToken
屬性後再用 IgnoreAntiforgeryToken
屬性覆寫,不如使用 自動驗證防偽權杖 屬性。 此屬性的運作方式與 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 篩選器用於在特定動作或控制器中取消對防偽權杖的需求。 當套用時,此篩選條件會覆寫在較高層級上指定的 ValidateAntiForgeryToken
和 AutoValidateAntiforgeryToken
篩選條件 (在全域或控制器上)。
[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 也可以透過對伺服器發出的額外請求(通常是 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 攻擊的範例:
使用者使用表單驗證登入
www.good-banking-site.example.com
。 伺服器會驗證使用者,並發出回應,其中包含驗證 cookie。 該網站很容易受到攻擊,因為它信任任何包含有效驗證 cookie 的請求。使用者造訪惡意網站
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 的「跨網站」部分。使用者選取 [提交] 按鈕。 當瀏覽器發出請求時,它會自動包含所請求網域cookie的驗證
www.good-banking-site.example.com
。要求會以使用者的驗證內容在
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。 在後續的要求中,中介軟體會驗證 cookie、重新建立主體,並將主體指派給 HttpContext.User 屬性。
令牌驗證
當使用者經過驗證時,會向他們發出權杖 (不是防偽權杖)。 權杖包含使用者資訊,可以是以宣告的形式,或是作為將應用程式導向其所維護之使用者狀態的參考權杖。 當使用者嘗試存取需要驗證的資源時,權杖會以持有人權杖的形式傳送至具有額外授權標頭的應用程式。 此方法可讓應用程式變成無狀態。 在每個後續要求中,令牌會傳入伺服器端驗證的要求中。 此令牌未經加密;而是編碼。 在伺服器上,權杖會被解碼以存取其資訊。 若要在後續的要求上傳送權杖,請將權杖儲存在瀏覽器的本機儲存體中。 如果權杖儲存在瀏覽器的本機儲存體中,不用擔心 CSRF 漏洞。 當權杖儲存於 cookie 時,需注意 CSRF。 如需詳細資訊,請參閱 GitHub 問題 SPA 程式碼範例會新增兩個 Cookie。
裝載在一個網域的多個應用程式
共用主機環境容易受到會話劫持、登入 CSRF 和其他攻擊的影響。
雖然 example1.contoso.net
和 example2.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
屬性明確停用防偽令牌:<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 頁)。 ASP.NET Core MVC 和 Razor Pages 範本中的所有表單都會產生防偽權杖。 下列一對檢視範例會產生防偽權杖:
<form asp-controller="Todo" asp-action="Create" method="post">
...
</form>
@using (Html.BeginForm("Create", "Todo"))
{
...
}
將防偽憑證明確新增至 <form>
元素,而不使用 Tag Helpers 搭配 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;
});
使用 Cookie
類別的屬性來設定防偽 CookieBuilder 屬性,如下表所示。
選項 | 描述 |
---|---|
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
- 頭
- 選項
- 追蹤
我們建議在非 API 案例中廣泛使用 AutoValidateAntiforgeryToken
。 此屬性可確保 POST 動作預設受到保護。 預設情況下,替代方案會忽略防偽權杖,除非將 ValidateAntiForgeryToken
套用到個別的動作方法。 在此案例中,POST 動作方法可能會因錯誤而不受保護,讓應用程式容易受到 CSRF 攻擊。 所有 HTTP POST 請求都應該傳送驗證權杖。
API 沒有自動機制來傳送非 cookie 令牌的部分。 實作可能取決於用戶端程式代碼實作。 以下顯示一些範例:
類別層級範例:
[Authorize]
[AutoValidateAntiforgeryToken]
public class ManageController : Controller
{
全域範例:
services.AddControllersWithViews(options =>
options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute()));
覆寫全域或控制器防偽屬性
IgnoreAntiforgeryToken 篩選器可用來消除指定動作 (或控制器) 的防偽權杖需求。 當篩選條件被套用時,此篩選條件會覆寫在較高層級指定的 ValidateAntiForgeryToken
和 AutoValidateAntiforgeryToken
篩選條件(無論是全域性的還是在控制器上的)。
[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
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
設定,則不會驗證補充資料。