作者如下: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 Core 會話中介軟體無關。
使用者可以採取預防措施來防範 CSRF 弱點:
- 使用完 Web 應用程式時登出。
- 會定期清除瀏覽器 Cookie。
不過,CSRF 弱點基本上是 Web 應用程式,而不是使用者的問題。
驗證基本概念
Cookie 型的驗證是一種熱門的驗證形式。 基於令牌的驗證系統日益受到歡迎,尤其是單頁應用(SPA)。
Cookie 型的驗證
當使用者以其使用者名稱和密碼進行驗證時,系統會發出包含驗證票證的權杖。 該令牌可用於驗證和授權。 令牌會儲存為 cookie,並隨著用戶端發出的每個要求一起傳送。 產生及驗證此 cookie 是由 cookie 認證中介軟體執行。 中介軟體會將使用者主體序列化為加密的 cookie。 在後續的要求中,中介軟體會驗證 cookie、重新建立主體,並將主體指派給 HttpContext.User 屬性。
基於令牌的驗證
當使用者經過驗證時,會向他們發出權杖 (不是防偽權杖)。 權杖包含使用者資訊,可以是宣告的形式,也可以是指向應用程式中儲存使用者狀態的參考權杖。 當使用者嘗試存取需要認證的資源時,令牌會以額外的授權標頭 Bearer 形式傳送給應用程式。 此方法可讓應用程式變成無狀態。 在每次後續請求中,伺服器端驗證的請求會包含令牌。 此令牌不是加密的,而是編碼的。 在伺服器上,權杖會被解碼以存取其資訊。 若要在後續的要求上傳送權杖,請將權杖儲存在瀏覽器的本機儲存體中。 將權杖放在瀏覽器本機儲存體中,並加以擷取,並將其視為持有人權杖使用,可保護 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 信任關係可供惡意探索。
擷取元資料標頭
現代瀏覽器會為每個請求附加 Fetch Metadata 請求標頭——其中最重要的是 Sec-Fetch-Site。
Sec-Fetch-Site描述發起請求的來源與被請求的來源之間的關係:same-origin識別網站向自身提出的請求,而same-sitecross-site識別由另一個來源發起的請求。
Origin標頭承載起始起點,並作為 Fetch Metadata 出現前瀏覽器的備援。
Sec-Fetch-Site 和 Origin 是 禁止請求標頭:瀏覽器會設定它們,且在頁面中執行的 JavaScript 無法覆蓋或偽造它們。 這使它們成為一個值得信賴的訊號,用來區分網站自身的請求與沒有伺服器發出令牌的跨站請求。 ASP.NET Core內建的自動CSRF保護機制會利用此訊號拒絕未明確信任的跨站表單貼文。
ASP.NET Core 中的自動 CSRF 保護
ASP.NET Core 隨附一個可自動提供 CSRF 保護的中介軟體,在使用 WebApplication.CreateBuilder 建置的應用程式中預設會啟用。 與 基於代幣的反偽造系統不同,這個中介軟體不會發出或驗證代幣。 取而代之的是檢查 Sec-Fetch-Site 和 OriginFetch 的元資料標頭 ,並記錄請求的驗證判決。 處理提交表單資料的元件會執行這個判決,拒絕未明確信任的跨來源表單貼文。
大多數應用程式不需要修改程式碼:同源瀏覽器請求、安全的 HTTP 方法,以及非瀏覽器用戶端(curl伺服器對伺服器、行動應用程式)都能順利通過,且不受影響。 中介軟體主要影響接受瀏覽器跨來源表單貼文的應用程式,例如網站將表單貼入不同來源的 API。 這些情境需要設定 CORS 來宣告受信任的來源,或是讓端點退出。
此中介軟體是基於代幣的反偽造系統 的補充 。 這兩種保護是共存的,且可以同時在同一端點同時啟用。 關於每種方式適用的時間比較,請參見 「與代幣反偽造的互動」。
運作原理
對於每個請求,中介軟體會評估一連串短規則以達成判斷——允許 或 拒絕。 檢定依序進行,第一組勝出:
-
安全的 HTTP 方法始終被允許,
GETHEAD且OPTIONSTRACE請求會通過。 此規定遵循 RFC 9110 §9.2.1 ,且與長期以來端點不應在 上改變狀態GET的規則相符。 - 允許使用
Sec-Fetch-Site: same-origin或Sec-Fetch-Site: none。 現代瀏覽器在每次請求時都會傳送Sec-Fetch-Site。same-origin涵蓋一般的應用程式內導覽與取物,以及none使用者直接發起的請求(輸入網址、使用書籤)。 這是最常見的程式碼路徑——大多數合法的瀏覽器流量都從這裡出口。 - 允許使用來自 CORS 的可信且有認證的來源。 如果請求帶有
Origin標頭,且端點解析的 CORS 政策信任該來源且政策已.AllowCredentials()設定,則請求被允許。 中介軟體會以與 CORS 中介軟體相同的方式解析原則:先使用來自[EnableCors("name")]的每個端點原則,然後使用透過AddDefaultPolicy註冊的預設原則。 請參閱 「允許跨來源客戶 」以了解此規則的重要限制。 - 其他
Sec-Fetch-Site任何數值都被否定。 當Sec-Fetch-Site是cross-site或same-site且來源未被 CORS 信任時,請求會被拒絕。 -
沒有
Sec-Fetch-Site,但存在Origin:中介軟體會將Origin與根據請求建構的scheme://host[:port]進行比較。 若匹配,請求即被允許;否則就會被拒絕。 這是供早於 Fetch Metadata 規範(約於 2020 年發布)的瀏覽器使用的後備路徑。 - 沒有
Sec-Fetch-Site,也沒有Origin:該請求會被允許。 瀏覽器在寫入請求時一定至少會傳送其中一者,因此,兩者都缺少的請求幾乎可以肯定是非瀏覽器用戶端,例如curl、Postman、行動應用程式或伺服器對伺服器的呼叫端。 CSRF 是僅限瀏覽器的攻擊向量,因此這些請求會通過。
中介軟體會將此判定記錄在該請求上,而不是自行結束該請求。 關於拒絕判決何時以及如何轉為 HTTP 400 Bad Request 回應,請參見 延遲驗證。
延遲驗證
中介軟體不會自動拒絕請求。 相反地,它會將其判定結果記錄在請求的 IAntiforgeryValidationFeature 上——也就是以權杖為基礎的防偽造系統所使用的相同功能——而被拒絕的判定結果會記錄為 無效。 這個請求還在流程中持續進行。 只有在處理表單資料的元件偵測到無效的判定結果時,它才會變成 HTTP 400 Bad Request。 這種延後處理方式與基於權杖的系統目前的運作方式一致:判定結果會提早產生,但要到表單被提交時才會套用。
以下元件會讀取 IAntiforgeryValidationFeature,並在已記錄的判定無效時以 400 - Bad Request 拒絕請求:
- MVC 行動受防偽造保護。
- 最小限度的 API 端點,綁定表單參數。
- Blazor SSR 端點。
- 任何直接讀取請求表單的程式碼,可作為最後一道防線。
每個使用端在信任判定結果之前,都會先確認防偽或 CSRF 中介軟體確實已執行,因此若管線未使用這兩種中介軟體中的任一者,就不會產生錯誤的拒絕。
這個模型造成的一個後果是,即使判定結果無效,從不讀取表單資料的端點仍會執行。 例如,一個從 JSON 綁定主體的 JSON API 端點,或忽略請求主體的處理器,在跨來源請求時不會自動被拒絕。 判定結果仍會記錄在 IAntiforgeryValidationFeature 上,供想要檢查它的程式碼查看,但不會有任何機制強制執行它。 CSRF 是一種涉及表單與cookie 的攻擊向量,因此,不處理由瀏覽器提交之表單的端點通常不需要這種拒絕機制。 會處理表單的端點——Razor Pages、MVC 檢視、Blazor SSR 和 Minimal API 表單繫結——會自動受到保護。
預設行為
中介軟體會由 WebApplication.CreateBuilder 自動註冊,並在驗證與授權之後執行。 它會使用註冊 ICsrfProtection 實作驗證每個請求,該實作預設會套用《 How it Work》中描述的規則。 預設實作可以被替換;參見 「客製化:實作 ICsrfProtection」。 要完全關閉中介軟體,請參考 「全域停用」。
結果是,擁有以下表單處理端點的最小應用程式已經受到保護:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapPost("/widgets", ([FromForm] Widget w) =>
Results.Created($"/widgets/{w.Id}", w));
app.Run();
發送同源 POST /widgets 請求的瀏覽器正常抵達端點。 位於 https://attacker.example.com 的瀏覽器在提交相同的表單時,會在端點繫結表單時、處理常式主體執行之前,以 400 - Bad Request 遭到拒絕。 不含curl或Sec-Fetch-Site的Origin請求是允許的。
由於拒絕會 延後給表單使用者,不讀取表單資料的端點——例如將 JSON 與 JSON 綁定其主體的 API——即使跨來源請求也不會自動被拒絕。 判定結果仍會記錄在想要檢查該請求的程式碼中。
中介軟體整合現有的防偽造模型:
-
最小 API:在端點上呼叫
.DisableAntiforgery(),會使該端點不使用這兩者:權杖型中介軟體和 CSRF 保護中介軟體。 兩者都會檢查相同的元資料(IAntiforgeryMetadata { RequiresValidation = false })。 -
MVC 控制器與操作:
[IgnoreAntiforgeryToken]同時也讓終端機退出了這兩種保護。
允許跨來源用戶端
最常見且需要採取動作的情況,是以瀏覽器為基礎的用戶端提交跨來源表單,例如位於 https://app.contoso.com 的網站將表單提交到位於 https://api.contoso.com 的 API。 這類表單提交預設會遭到拒絕,因為 Sec-Fetch-Site 是 same-site 或 cross-site,而不是 same-origin,而表單使用端會以 400 - Bad Request 強制執行該判定。
CSRF 中介軟體並未引入自己的信任清單。 它會沿用 CORS 中介軟體為端點解析出的同一個 CORS 原則:如果該原則允許請求的 Originand,且該原則已設定 .AllowCredentials(),CSRF 中介軟體就會為該請求記錄允許的判定。
允許帶有 WithOrigins 的來源,本身並不表示該來源的瀏覽器 JavaScript 可以讀取附帶認證資訊之請求的回應內容。
.AllowCredentials() 授予該許可。 省略 CORS 並不保證請求會無憑證而來,因此僅靠 CORS 並不能作為對抗 CSRF 的辯護。 CSRF 中介軟體需要同時具備允許的來源,以及作為明確信任訊號的 .AllowCredentials()。 否則,該請求會繼續進入其餘的 Sec-Fetch-Site 檢查以及 Origin-vs-Host 檢查。
政策依序依端點選擇:
-
[EnableCors("api")](MVC)或.RequireCors("api")(最小API)→命名的政策"api"。 - 端點上沒有 CORS 中繼資料 → 會套用以
AddDefaultPolicy註冊的預設原則。 - 沒有匹配政策(命名政策未註冊、沒有預設政策或
services.AddCors()從未被呼叫)→沒有基於 CORS 衍生的信任。 中介軟體則屬於Sec-Fetch-SiteOrigin-vs-Host 規則。
使用預設政策與最小 API 端點的最小範例:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
options.AddDefaultPolicy(policy =>
policy.WithOrigins("https://app.contoso.com")
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials());
});
var app = builder.Build();
app.UseCors();
app.MapPost("/widgets", ([FromForm] Widget w) =>
Results.Created($"/widgets/{w.Id}", w));
app.Run();
針對單一端點的命名政策:
app.MapPost("/widgets", ([FromForm] Widget w) => Results.Created($"/widgets/{w.Id}", w))
.RequireCors("api");
警告
AllowAnyOrigin 不會被刻意視為 CSRF 信任信號。 (在 ASP.NET Core CORS 中,將 AllowAnyOrigin 與 .AllowCredentials() 結合使用是無效且不安全的。)AllowAnyOrigin 的意思是「任何瀏覽器都可以讀取此資源」,而這與「任何來源都可以代表使用者變更狀態」是不同的考量。如果將 AllowAnyOrigin 視為受信任的,這個中介軟體對跨來源寫入就會形同不起作用。 需要允許公開讀取的 CORS 政策並以 CSRF 保護寫入的應用程式,應明確列出受信任的寫入來源並搭配 WithOrigins,或者如果不依賴基於 的驗證,則 cookie。
明確列出特定來源 WithOrigins 但省略 .AllowCredentials() 的政策,對於 CSRF 信任機制也不會被接受。 將 .AllowCredentials() 加入任何用於將跨來源、經 cookie 驗證的表單提交標記為受 CSRF 信任的政策中。
在端點上使用 [DisableCors] 並不表示可豁免 CSRF。它會略過源自 CORS 的信任判定步驟,而該請求仍須符合 Sec-Fetch-Site 與 Origin-vs-Host 規則。 欲選擇退出 CSRF 保護,請參見 「選擇退出端點」。
關於如何設定 CORS 本身——AddCors、AddDefaultPolicy、AddPolicyWithOrigins以及政策建構 API 的其他部分——請參閱在 ASP.NET Core 中啟用跨來源請求(CORS)。
將端點設為退出狀態
如果某端點無法被瀏覽器存取,或是由非機制cookie (如承載憑證或 API 金鑰)保護,請個別選擇退出,而不是全局停用中介軟體。
最小 API — 在端點或群組呼叫 DisableAntiforgery :
app.MapPost("/api/webhook", (WebhookPayload p) => Results.Accepted())
.DisableAntiforgery();
MVC 控制器 — 將 [IgnoreAntiforgeryToken] 套用至動作方法或控制器:
[ApiController]
[Route("api/[controller]")]
[IgnoreAntiforgeryToken]
public class WebhookController : ControllerBase
{
[HttpPost]
public IActionResult Post([FromBody] WebhookPayload payload) => Accepted();
}
這兩種方式都會將 IAntiforgeryMetadata { RequiresValidation = false } 加到端點上,而 CSRF 中介軟體會遵循此設定,略過驗證。
警告
只有當端點不易受到 CSRF 攻擊時,才應該關閉 CSRF 保護——例如,無法從瀏覽器呼叫的端點,或是透過非cookie 認證方式(如承載憑證或 API 金鑰)保護。 不要在依賴 Cookie 認證的瀏覽器可存取端點上關閉 CSRF 保護。
全域停用
可透過 DisableCsrfProtection 設定鍵,在整個應用程式中停用中介軟體。 這是一個逃生出口——偏好每個端點的退出選項。
在 appsettings.json 中:
{
"DisableCsrfProtection": true
}
或者作為環境變數:
ASPNETCORE_DisableCsrfProtection=true
當此鍵設為 true時, WebApplication 會跳過在管線中註冊中介軟體。
ICsrfProtection服務仍是註冊狀態,因此任何直接解決該問題的程序仍能正常運作。
警告
自動 CSRF 中介軟體同時滿足需要驗證端點的防偽造要求,即使應用程式未呼叫 app.UseAntiforgery()。 如果應用程式仰賴反偽造機制卻未呼叫 app.UseAntiforgery(),那麼若全域停用 CSRF 中介軟體,或是在不是以 WebApplication 建置、因而未注入該中介軟體的主機上執行,這些端點就不會有任何反偽造中介軟體保護。 對這類端點發出的請求就會擲回例外。 在該組態中呼叫 app.UseAntiforgery()。
瀏覽器支援
Sec-Fetch-Site 目前所有基於 Chromium 的瀏覽器版本、Firefox 和 Safari 都支援此功能。 如需最具權威性的相容性表,請參閱關於 Sec-Fetch-Site 的 MDN 參考資料。
在 Fetch Metadata 之前的舊瀏覽器不會傳送 Sec-Fetch-Site。 對於這些用戶端,中介軟體會改為將 Origin 標頭與請求的通訊協定和主機進行比對。 瀏覽器多年來一直會在跨來源寫入請求中傳送 Origin,因此這種後備機制涵蓋了幾乎所有來自舊版瀏覽器的流量。
非瀏覽器用戶端——curl、Postman、行動應用程式、伺服器對伺服器呼叫端——通常既不會傳送 Sec-Fetch-Site,也不會傳送 Origin。 這些請求被允許,因為 CSRF 是僅瀏覽器使用的攻擊向量,依賴瀏覽器自動附加環境憑證(如 Cookie)。 非瀏覽器客戶端若想攻擊 API,則不需要 CSRF;它可以直接用它擁有的憑證呼叫 API。
自訂:實作 ICsrfProtection
決策邏輯存在於單一方法介面之中:
namespace Microsoft.AspNetCore.Antiforgery;
public interface ICsrfProtection
{
ValueTask<CsrfProtectionResult> ValidateAsync(HttpContext context);
}
要替換預設實作,請在 DI 中註冊一個單例。 由於框架使用 TryAddSingleton,明確 AddSingleton 呼叫會覆蓋預設值:
builder.Services.AddSingleton<ICsrfProtection, AllowlistCsrfProtection>();
當信任模型不符合 CORS 時,自訂實作非常有用——例如當偏好固定的合作夥伴來源允許清單,或需要更嚴格規則時:
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Http;
public sealed class AllowlistCsrfProtection : ICsrfProtection
{
private static readonly HashSet<string> SafeMethods =
new(StringComparer.OrdinalIgnoreCase) { "GET", "HEAD", "OPTIONS", "TRACE" };
private static readonly HashSet<string> TrustedOrigins =
new(StringComparer.OrdinalIgnoreCase)
{
"https://app.contoso.com",
"https://admin.contoso.com",
};
public ValueTask<CsrfProtectionResult> ValidateAsync(HttpContext context)
{
if (SafeMethods.Contains(context.Request.Method))
{
return ValueTask.FromResult(CsrfProtectionResult.Allowed());
}
var origin = context.Request.Headers.Origin.ToString();
var allowed = !string.IsNullOrEmpty(origin) && TrustedOrigins.Contains(origin);
return ValueTask.FromResult(
allowed ? CsrfProtectionResult.Allowed() : CsrfProtectionResult.Denied());
}
}
中介軟體仍然會遵循 .DisableAntiforgery() / [IgnoreAntiforgeryToken],無論註冊了哪個實作——退出機制會由中介軟體本身在呼叫 ValidateAsync 之前處理。
與基於代幣的反偽造的互動
這兩種CSRF防禦針對不同層級,設計上能共存。 它們也具有相同的請求特性:兩者都會將結果記錄在 IAntiforgeryValidationFeature 上,而表單消費者會強制套用其中現有的任何判定。
| 層面 | 基於標記 AntiforgeryMiddleware |
自動的 CSRF 防護中介軟體 |
|---|---|---|
| 介紹 | ASP.NET Core 2.0+ | .NET 11 |
| Activation | 透過app.UseAntiforgery()選擇加入(或藉由AddMvc / MapRazorPages / AddRazorComponents而隱含選擇加入) |
由 WebApplication.CreateBuilder 自動注入 |
| 驗證 | 同步標記(表單欄位 + cookie 配對) |
Sec-Fetch-Site
/
Origin 標頭 |
| Requires | ASP.NET Core 令牌加密資料保護概述 | 沒有代幣,就沒有狀態 |
| 瀏覽器範圍 | 所有發送 Cookie 的瀏覽器 | 支援所有現代瀏覽器;Origin 舊版瀏覽器的後備方案 |
| 每個端點的選擇退出 | .DisableAntiforgery() / [IgnoreAntiforgeryToken] |
我也是——兩者都尊重相同的元資料 |
基於憑證的中介軟體專門防範經典的 CSRF 攻擊模式,即惡意網站利用使用者的環境 Cookie 觸發一個表單 POST 到易受攻擊的網站。 自動 CSRF 中介軟體利用瀏覽器提供的元資料,在 HTTP 層處理相同的威脅。 兩者都可以在同一端點同時啟用,許多應用程式將從深度防禦中受益:
- Razor 已使用權杖系統的 Pages、MVC 和 Blazor SSR 應用程式,會在權杖驗證之前先執行根據標頭進行的檢查,而不變更權杖流程。
- 會繫結表單的 Minimal API 應用程式無須呼叫
app.UseAntiforgery(),也不必在各端點間傳遞IAntiforgery,就能取得實用的預設行為。 - 由跨來源 SPA 呼叫的 API 可以依靠這個中介軟體搭配 CORS 允許清單;如果 API 從不提供 HTML 表單,便可完全略過權杖系統。
自動 CSRF 中介軟體在許多情況下取代了基於令牌的系統,因為兩者都保護相同的表單處理端點。 以下情況下,保留基於代幣的系統:
- 應用程式必須支援不發送
Sec-Fetch-Site的瀏覽器。 請參閱 瀏覽器支援。 - 此應用程式會使用 IAntiforgeryAdditionalDataProvider 在代幣中往返傳送額外資料。
- 安全審查或合規要求將令牌防禦指定為獨立層。
關於基於權杖系統的細節,包括表單整合、AJAX 流程、透過 AntiforgeryOptions及 IAntiforgery API 的配置,請參閱 ASP.NET Core 中的防偽造。
代幣驗證優先
當應用程式呼叫 app.UseAntiforgery()時,基於標記的中介軟體會在自動 CSRF 中介軟體之後執行。 令牌中介軟體會清除 CSRF 中介軟體記錄的任何判定,並以令牌驗證結果取代。 代幣結果具有權威性:
- 若 CSRF 中介軟體被標記為無效,且請求帶有有效標記,則該請求即為有效。
- CSRF 中介軟體允許的請求若缺少或無效,則標記為無效。
這種排序意味著使用令牌系統的應用程式會看到與自動中介軟體出現前相同的端對端行為,而不使用令牌的應用程式則會退回到 CSRF 中介軟體的判斷。
Blazor 靜態伺服器端渲染
Blazor 靜態伺服器端渲染(SSR)端點也參與相同的延遲模型。
Razor 元件端點會信任上游中介軟體在 IAntiforgeryValidationFeature 上記錄的判定結果,且僅在該判定結果無效時,才會對表單 POST 要求傳回 400 - Bad Request。 端點不再自行驗證請求。
行為取決於執行的中介軟體:
- 呼叫
app.UseAntiforgery()的應用程式保持不變。 基於憑證的中介軟體會驗證每個請求,並像之前一樣為渲染表單產生防偽造標記。 - 無法呼叫
app.UseAntiforgery()的應用程式則會由自動 CSRF 中介軟體保護。 在此配置下,端點跳過反偽造令牌產生,因為沒有令牌中介軟體能在後續請求時驗證令牌。
這是針對先前會移除 app.UseAntiforgery() 的靜態 SSR 所做的行為變更:它們現在會受到 CSRF 中介軟體保護,而不是維持未受保護的狀態,並且不再產生防偽權杖。 關於遷移指引,請參見從 .NET 10 中的 ASP.NET Core 遷移到 .NET 11 中的 ASP.NET Core。 如需正式的重大變更通知,請參閱 Blazor 伺服器端轉譯將防偽要求驗證延後交由中介軟體處理。
Troubleshooting
症狀: 來自瀏覽器的同來源請求成功,但跨來源表單貼文卻 400 - Bad Request 沒有正文返回。
原因: CSRF 中介軟體對跨來源請求記錄了無效的判定結果,而表單處理元件(例如 MVC 動作方法、Minimal API 表單繫結,或 Blazor SSR 表單張貼)則會以 400 - Bad Request 強制套用該判定結果。 這是處理表單的端點的預期預設行為。
解決: 根據情境,請選擇以下其中一項:
- 如果呼叫來源已知且受信任,請透過 CORS 允許。
- 如果端點無法由瀏覽器存取,或使用非 cookie 驗證,請使用 或
.DisableAntiforgery()[IgnoreAntiforgeryToken]。 - 如果整個應用程式都需要停用此功能(例如在遷移期間),請全域停用。
診斷:中介軟體會在類別 Debug 下,以事件名稱 Microsoft.AspNetCore.Antiforgery.CsrfProtectionMiddleware 於 CsrfValidationFailed 層級記錄每一個無效判定。 在 Debug 中為該類別啟用 appsettings.Development.json 記錄:
{
"Logging": {
"LogLevel": {
"Microsoft.AspNetCore.Antiforgery.CsrfProtectionMiddleware": "Debug"
}
}
}
記錄下來的判決會顯示在日誌中:
dbug: Microsoft.AspNetCore.Antiforgery.CsrfProtectionMiddleware[1]
Cross-origin CSRF protection marked request POST /widgets from origin 'https://attacker.example.com' as invalid.
在本機重現: 使用 curl 搭配明確的 Origin 標頭,向表單端點模擬跨來源瀏覽器請求:
curl -i -X POST https://localhost:{PORT}/widgets \
-H "Origin: https://attacker.example.com" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "name=test"
用應用程式的本地 HTTPS 埠來取代 {PORT} 。 之所以會觀察到 400 - Bad Request,是因為端點會綁定表單,從而強制套用已記錄的判定。 非表單端點會回傳其正常回應,因為沒有任何東西會讀取該判定結果。 若沒有標頭, Origin 則允許相同的請求,因為 curl 不會發送 Sec-Fetch-Site 任何標頭,且沒有標頭的請求會被視為非瀏覽器客戶端。
本文其餘部分描述的基於代幣的反偽造系統早於此中介軟體,且仍然可用。 對大多數應用程式來說,自動保護本身就足夠了。 關於何時保留權杖系統以及如何遷移的指引,請參見「從 .NET 10 中的 ASP.NET Core 遷移到 .NET 11 中的 ASP.NET Core」。
ASP.NET Core 中的防偽機制
警告
ASP.NET Core 會使用 ASP.NET Core 資料保護來實作防偽。 資料保護堆疊必須設定為在伺服器陣列中運作。 如需詳細資訊,請參閱設定資料保護。
當呼叫以下 API 之一時Program.cs,抗偽造服務會在相依注入容器中註冊:
注意
註冊服務不會把反偽造中介軟體加入請求處理流程。 MVC 和 Razor Pages 用內建過濾器驗證代幣,因此不需要中介軟體。
Blazor 和最小 API 都需要在 Program.cs 中明確呼叫 UseAntiforgery,而這項呼叫在 Blazor Web App 專案範本中預設已存在。 欲了解更多資訊,請參閱 ASP.NET Core Blazor 認證與授權,以及以最小 API 進行防偽造。
如需詳細資訊,請參閱使用基本 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:
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。
Cookies 關於認證或授權,請使用比 CookieSecurePolicy.None更強的政策。
你也可以選擇在非cookie環境中,透過 HTTPS 僅使用安全套接層(SSL),來保護防偽造Development。在應用程式檔案中的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 篩選條件可用來消除指定動作的防偽權杖需求 (或控制器)。 套用此篩選器時,它會覆寫在較高層級(全域或控制器)設定的 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 在 DI 中註冊防偽造服務,並將 UseAntiforgery Antiforgery 中介軟體加入請求處理流程。 防偽代幣可用來緩解跨網站請求偽造攻擊。
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 方法是 POST、PUT 或 PATCH 類型的相關 HTTP 方法 。
- 要求與有效的端點相關聯。
防偽造中介軟體不會讓請求流程短路。 端點程式碼總是會執行,即使憑證驗證失敗。 為了觀察代幣驗證的結果,請從 IAntiforgeryValidationFeature 中解析 HttpContext.Features,並檢查其 IsValid 屬性或者 Error 屬性以獲取失敗細節。 當端點需要自訂處理防偽驗證失敗時,此方法非常有用。
注意: 手動啟用時,驗證和授權中介軟體之後必須執行防偽中介軟體,以防止使用者未經驗證時讀取表單資料。
預設情況下,接受表單資料的最小 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();
警告
呼叫 .DisableAntiforgery() 會停用端點的跨站請求偽造(CSRF)保護。 此方法僅在端點不易受CSRF攻擊時使用,例如:
- 無法從瀏覽器呼叫的端點(例如內部 API)
- 以非cookie基礎認證(例如承載憑證或 API 金鑰)保護的端點
- 不依賴使用者 Cookie 的內部或基礎設施端點
請勿關閉依賴 Cookie 認證或處理使用者提交表單資料的瀏覽器可存取端點的反偽造驗證功能,因為這會使您的應用程式暴露於 CSRF 攻擊之下。
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();
提交表單時,沒有有效的防偽權杖:
- 在
Development環境中,會拋出例外。 -
Production在環境中,訊息會被記錄下來。
HTTP 方法的限制與 HttpMethodOverrideMiddleware 互動
針對中介軟體的防偽造路徑,AntiforgeryMiddleware且UseAntiforgery()僅驗證 HTTP POST、PUT 和 PATCH 請求的防偽造令牌。 其他 HTTP 方法,如 DELETE,則不會自動驗證。
若要驗證其他 HTTP 方法的防偽權杖,請從 DI 解析 IAntiforgery,並明確呼叫 ValidateRequestAsync 或 IsRequestValidAsync:
app.MapDelete("/item/{id}", async (int id, IAntiforgery antiforgery, HttpContext context) =>
{
await antiforgery.ValidateRequestAsync(context);
// Process the DELETE request
});
警告
當 HttpMethodOverrideMiddleware 設定為( FormFieldName 表單欄位模式)並置於 AntiforgeryMiddleware之前時,POST 請求可被覆寫為 DELETE(或其他未驗證的方法)。 由於 AntiforgeryMiddleware 僅驗證 POST、PUT 和 PATCH,覆寫請求可繞過防偽造驗證。
為了保護這些端點:
- 如果您的管線允許,建議將
HttpMethodOverrideMiddleware放在完成防偽造驗證之後。 - 避免對依賴防偽造驗證的端點進行表單欄位覆寫。
- 若必須先執行表單欄位覆寫,請使用
IAntiforgery.ValidateRequestAsync明確驗證防偽權杖。
有關設定細節,請參見 ASP.NET Core 中介軟體。
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。 該網站很容易受到攻擊,因為它信任所有包含有效驗證的請求。使用者造訪惡意網站
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 Core 會話中介軟體無關。
使用者可以採取預防措施來防範 CSRF 弱點:
- 使用完 Web 應用程式時登出。
- 會定期清除瀏覽器 Cookie。
不過,CSRF 弱點基本上是 Web 應用程式,而不是使用者的問題。
驗證基本概念
Cookie 型的驗證是一種熱門的驗證形式。 基於令牌的驗證系統日益受到歡迎,尤其是單頁應用(SPA)。
Cookie 型的驗證
當使用者使用其使用者名稱和密碼進行驗證時,會向他們頒發一個權杖,其中包含可供驗證及授權使用的驗證票證。 令牌會儲存為 cookie,並隨著用戶端發出的每個要求一起傳送。 此 cookie 的產生與驗證由 cookie 驗證中介軟體執行。 中介軟體會將使用者主體序列化為加密的 cookie。 在後續的要求中,中介軟體會驗證 cookie、重新建立主體,並將主體指派給 HttpContext.User 屬性。
基於令牌的驗證
當使用者經過驗證時,會向他們發出權杖 (不是防偽權杖)。 權杖包含使用者資訊,可以是宣告的形式,也可以是指向應用程式中儲存使用者狀態的參考權杖。 當使用者嘗試存取需要認證的資源時,令牌會以額外的授權標頭 Bearer 形式傳送給應用程式。 此方法可讓應用程式變成無狀態。 在每次後續請求中,伺服器端驗證的請求會包含令牌。 此令牌不是加密的,而是編碼的。 在伺服器上,權杖會被解碼以存取其資訊。 若要在後續的要求上傳送權杖,請將權杖儲存在瀏覽器的本機儲存體中。 將權杖放在瀏覽器本機儲存體中,並加以擷取,並將其視為持有人權杖使用,可保護 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屬性明確停用防偽 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 支援。
多個瀏覽器分頁和同步器令牌模式
使用同步器權杖模式時,只有最近載入的頁面包含有效的防偽權杖。 使用多個分頁可能會有問題。 例如,如果使用者開啟多個分頁:
- 只有最近載入的分頁包含有效的防偽權杖。
- 從先前載入的分頁提出的要求失敗,並出現錯誤:
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。
Cookies 關於認證或授權,請使用比 CookieSecurePolicy.None更強的政策。
你也可以選擇在非cookie環境中,透過 HTTPS 僅使用安全套接層(SSL),來保護防偽造Development。在應用程式檔案中的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 篩選條件可用來消除指定動作的防偽權杖需求 (或控制器)。 套用此篩選器時,它會覆寫在較高層級(全域或控制器)設定的 ValidateAntiForgeryToken 和 AutoValidateAntiforgeryToken 篩選器。
[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 不支援使用包含的篩選條件 (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 驗證時,應用程式端點必須像保護 Cookie 一樣防範 CSRF 攻擊。 瀏覽器會隱含地將驗證內容傳送至伺服器,而且必須保護端點免於 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 Core 會話中介軟體無關。
使用者可以採取預防措施來防範 CSRF 弱點:
- 使用完 Web 應用程式時登出。
- 會定期清除瀏覽器 Cookie。
不過,CSRF 弱點基本上是 Web 應用程式,而不是使用者的問題。
驗證基本概念
Cookie 型的驗證是一種熱門的驗證形式。 基於令牌的驗證系統日益受到歡迎,尤其是單頁應用(SPA)。
Cookie 型的驗證
當使用者使用其使用者名稱和密碼進行驗證時,會向他們頒發一個權杖,其中包含可供驗證及授權使用的驗證票證。 令牌會儲存為 cookie,並隨著用戶端發出的每個要求一起傳送。 此 cookie 的產生與驗證由 cookie 驗證中介軟體執行。 中介軟體會將使用者主體序列化為加密的 cookie。 在後續的要求中,中介軟體會驗證 cookie、重新建立主體,並將主體指派給 HttpContext.User 屬性。
基於令牌的驗證
當使用者經過驗證時,會向他們發出權杖 (不是防偽權杖)。 權杖包含使用者資訊,可以是宣告的形式,也可以是指向應用程式中儲存使用者狀態的參考權杖。 當使用者嘗試存取需要認證的資源時,令牌會以額外的授權標頭 Bearer 形式傳送給應用程式。 此方法可讓應用程式變成無狀態。 在每次後續請求中,伺服器端驗證的請求會包含令牌。 此令牌不是加密的,而是編碼的。 在伺服器上,權杖會被解碼以存取其資訊。 若要在後續的要求上傳送權杖,請將權杖儲存在瀏覽器的本機儲存體中。 如果令牌存儲在瀏覽器的本機存儲空間中,無需擔心 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屬性明確停用防偽 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 支援。
多個瀏覽器分頁和同步器令牌模式
使用同步器權杖模式時,只有最近載入的頁面包含有效的防偽權杖。 使用多個分頁可能會有問題。 例如,如果使用者開啟多個分頁:
- 只有最近載入的分頁包含有效的防偽權杖。
- 從先前載入的分頁提出的要求失敗,並出現錯誤:
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。
Cookies 關於認證或授權,請使用比 CookieSecurePolicy.None更強的政策。
你也可以選擇在非cookie環境中,透過 HTTPS 僅使用安全套接層(SSL),來保護防偽造Development。在應用程式檔案中的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 篩選條件可用來消除指定動作的防偽權杖需求 (或控制器)。 套用此篩選器時,它會覆寫在較高層級(全域或控制器)設定的 ValidateAntiForgeryToken 和 AutoValidateAntiforgeryToken 篩選器。
[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 攻擊的範例:
使用者使用表單驗證登入
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 Core 會話中介軟體無關。
使用者可以採取預防措施來防範 CSRF 弱點:
- 使用完 Web 應用程式時登出。
- 會定期清除瀏覽器 Cookie。
不過,CSRF 弱點基本上是 Web 應用程式,而不是使用者的問題。
驗證基本概念
Cookie 型的驗證是一種熱門的驗證形式。 基於令牌的驗證系統日益受到歡迎,尤其是單頁應用(SPA)。
Cookie 型的驗證
當使用者使用其使用者名稱和密碼進行驗證時,會向他們頒發一個權杖,其中包含可供驗證及授權使用的驗證票證。 令牌會儲存為 cookie,並隨著用戶端發出的每個要求一起傳送。 此 cookie 的產生與驗證由 cookie 驗證中介軟體執行。 中介軟體會將使用者主體序列化為加密的 cookie。 在後續的要求中,中介軟體會驗證 cookie、重新建立主體,並將主體指派給 HttpContext.User 屬性。
基於令牌的驗證
當使用者經過驗證時,會向他們發出權杖 (不是防偽權杖)。 權杖包含使用者資訊,可以是宣告的形式,也可以是指向應用程式中儲存使用者狀態的參考權杖。 當使用者嘗試存取需要認證的資源時,令牌會以額外的授權標頭 Bearer 形式傳送給應用程式。 此方法可讓應用程式變成無狀態。 在每次後續請求中,伺服器端驗證的請求會包含令牌。 此令牌不是加密的,而是編碼的。 在伺服器上,權杖會被解碼以存取其資訊。 若要在後續的要求上傳送權杖,請將權杖儲存在瀏覽器的本機儲存體中。 如果令牌存儲在瀏覽器的本機存儲空間中,無需擔心 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屬性明確停用防偽 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-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 包含三個用於防偽權杖的篩選器:
防偽選項
自訂 AntiforgeryOptions 於 Startup.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。
Cookies 關於認證或授權,請使用比 CookieSecurePolicy.None更強的政策。
您可以選擇在非cookie環境中,僅透過 HTTPS 來使用安全套接層(SSL)保護防偽造Development,請在應用程式的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 篩選條件可用來消除指定動作的防偽權杖需求 (或控制器)。 套用此篩選器時,它會覆寫在較高層級(全域或控制器)設定的 ValidateAntiForgeryToken 和 AutoValidateAntiforgeryToken 篩選器。
[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 設定,則不會驗證補充資料。