跨站點請求偽造(又名CSRF或XSRF)是最常見的攻擊之一,用戶被欺騙通過他的瀏覽器代表他執行一個不需要的操作,在他當前通過身份驗證的一個站點中。ASP.Net Core包含一個Antiforgery包,可用於確保您的應用程序免受此特定風險的影響。
簡要的CSRF概述
CSRF攻擊依賴於以下事實:大多數身份驗證系統將在瀏覽器中存儲憑據(例如身份驗證cookie),這些憑據會隨着對特定網站域/子域的任何請求而自動發送。
攻擊者然后可以通過社交媒體,電子郵件和其他方式誘騙用戶進入惡意或受感染的站點,他們可以在其中向受攻擊或目標站點發送請求。如果用戶在目標站點上進行了身份驗證,則此請求將包含其憑據,並且該站點將無法將其與合法請求區分開來。例如,黑客可能會向打開惡意網站的用戶發送鏈接。在這個網站中,他們會欺騙用戶點擊一個按鈕,在用戶通過身份驗證的目標網站上發布隱藏表單。攻擊者將偽造這張表格,並將其張貼在一個類似於資金轉移網址的URL上,並且包含諸如將一些資金轉移到攻擊者帳戶的數據!圖1可視化地描述了CSRF的實際應用。
圖1:CSRF概述
理解這些請求對攻擊者有任何好處是很重要的,他們必須改變應用程序中的某些狀態。簡單地檢索一些數據對他們來說是無用的,就像發送請求並接收到響應的用戶及其瀏覽器一樣(即使他們不知道)。當然,攻擊的影響取決於具體的應用程序和實際的用戶權限,但它可以用來代表用戶嘗試資金轉賬或購買,代表他們發送消息,更改電子郵件/密碼,如果是管理用戶損失甚至會更大。
OWASP維護一個頁面,提供針對此次攻擊的推薦預防措施,其中包括:
- 驗證請求的來源
- 在每個請求中包含一個CSRF令牌(這應該是一個加密的偽隨機值,因此攻擊者本身不能偽造令牌)
可以在OWASP頁面上閱讀有關此攻擊的更多信息以及推薦的技術。開放Web應用安全項目(OWASP)是一個全球性的非營利性慈善組織,致力於提高軟件的安全性,將其核心目標定義為“成為推動全球社區安全和安全世界的軟件“。您可以在他們的關於頁面閱讀更多關於它們的信息。
ASP.Net Core Antiforgery如何工作
ASP.Net Core包含一個名為Antiforgery的包,可用於保護您的網站免受CSRF攻擊。 該包實現了OWASP站點推薦的CSRF令牌機制。更具體地說,它實現OWASP備忘單中描述的Double Submit Cookie和Encrypted Token Pattern的混合。這基本上意味着它提供了一個無狀態的防御機制,由兩個項目(或標記集)組成,這些項目應在Antiforgery包中驗證的任何請求中找到:
- 作為cookie包含的防偽令牌,以偽隨機值的形式生成,並使用新的Data Protection API進行加密
- 一個額外的標記包含作為表單域,標題或cookie。這包括相同的偽隨機值,加上來自當前用戶身份的附加數據。它也使用Data Protection API加密。
這些令牌將生成服務器端並與HTML文檔一起傳播到用戶的瀏覽器。當瀏覽器發送新的請求時,cookie標記將被默認包含,而應用程序需要確保請求標記也包含在內。 如果出現以下情況,請求將被拒絕:
- 2個令牌中的任何一個都丟失或者格式/加密格式不正確
- 它們的偽隨機值是不同的
- 嵌入在第二令牌中的用戶數據與當前認證的用戶不匹配
攻擊者無法自己偽造這些令牌。當他們被加密時,他需要在加密令牌之前破解加密。如果處於Web Farm場景中,那么檢查Data Protection API如何存儲密鑰以及如何使用應用程序標識符隔離它們對您來說非常重要。 因為當Web Farm的多個單獨實例使用不同的密鑰時,它們將無法知曉來自其他實例的秘鑰。 所以需要配置Data Protection API,以便實例共享相同的密鑰,從而能夠相互解密。 有關身份驗證cookie的示例,請參閱asp文檔。
圖2:合法請求中的防偽與CSRF攻擊請求
此外,無論何時令牌都會由服務器生成並包含在響應中,X-FRAME-OPTIONS標頭都包含在值SAMEORIGIN中。 這可以防止瀏覽器在可能嘗試點擊劫持攻擊的惡意網站內的iframe中呈現頁面。以下部分將詳細介紹如何在控制器操作中執行Antiforgery驗證,以及如何確保兩個令牌包含在請求中。
在您的ASP.NET Core應用程序中使用Antiforgery
為項目添加防偽技術
Microsoft.AspNetCore.Antiforgery軟件包已作為Microsoft.AspNetCore.Mvc的依賴項包含在內。 同樣,通過在Startup.ConfigureServices()方法中調用services.addMvc(),所需的Antiforgery服務會自動在DI容器中注冊。
仍然有些情況需要手動將Antiforgery添加到項目中:
1. 需要覆蓋默認Antiforgery選項,則需要手動調用services.AddAntiforgery(opts => {opts setup}),以便可以提供特定的選項設置。 這些選項包括諸如cookie標記的cookie名稱以及請求標記的表單字段或標題名稱等內容。
2. 在不使用MVC框架的情況下從頭開始編寫ASP.Net Core應用程序,則需要手動包含Antiforgery包並注冊服務。
請記住,這只是在項目中注冊Antiforgery並設置選項。 它不會自動啟用任何類型的請求驗證! 需要按照以下部分手動執行操作。
保護控制器的行為
即使Antiforgery已添加到項目中,令牌也不會自動生成,也不會根據任何請求進行驗證,除非啟用它們。 本節介紹如何將令牌驗證啟用為請求處理的一部分,而以下各節介紹如何生成令牌並確保它們包含在瀏覽器發送的請求中。可以通過依賴注入手動添加一些需要IAntiforgery實例的中間件,然后調用ValidateRequestAsync(httpContext),捕獲任何AntiforgeryValidationException並在此情況下返回400。
try { await _antiforgery.ValidateRequestAsync(context); await next.Invoke(); } catch (AntiforgeryValidationException exception) { context.Response.StatusCode = 400; }
如果使用的是MVC,那么會有Antiforgery特定的授權過濾器。 只需確保控制器操作使用以下屬性的組合來保護:
- [AutoValidateAntiforgeryToken] - 這會在任何“不安全”請求中添加防偽驗證,其中不安全意味着請求的方法不是GET,HEAD,TRACE和OPTIONS。 (因為其他HTTP方法是用於改變服務器狀態的請求)。 它將應用於全局(作為全局過濾器添加時)或類級別(添加到特定控制器類時)。
- [ValidateAntiForgeryToken] - 這用於將Antiforgery驗證添加到特定的控制器操作或類。 它不考慮請求HTTP方法。 因此,如果添加到類中,它會將驗證添加到所有操作,即使是那些具有GET,HEAD,TRACE或OPTIONS方法的驗證。
- [IgnoreAntiforgeryToken] - 在特定操作或控制器中禁用Antiforgery驗證。 例如,您可能會在全局或整個控制器類中添加Antiforgery驗證,但您可能仍希望忽略特定操作中的驗證。
可以混合和匹配這些屬性以滿足項目需求和團隊偏好。 例如:
- 可以添加AutoValidateAntiforgeryToken作為全局篩選器,並在極少數情況下使用IgnoreAntiforgeryToken,在這種情況下,可能需要為“不安全”請求禁用它。
- 可以將AutoValidateAntiforgeryToken添加到WebAPI風格的控制器(手動或通過約定),並使用MVC風格的控制器中的ValidateAntiforgeryToken處理許多GET請求,並返回一些不安全的請求,例如PUT和POST操作。
找到最適合項目的方法。 只要確保驗證適用於需要的每個操作。現在讓我們來看看如何確保令牌包含在瀏覽器發送的請求中。
在服務器端生成的表單中包含令牌
為了防偽驗證成功,需要確保cookie標記和請求標記都包含在將通過驗證的請求中。每當使用新的標記助手在Razor視圖中生成表單時,請求標記將自動包含為名為__RequestVerificationToken(在添加Antiforgery服務時可以在選項中設置不同的名稱)的隱藏字段。例如下面的Razor代碼:
<form asp-controller="Foo" asp-action="Bar"> … <button type="submit">Submit</button> </form>
將生成以下html:
<form action="/Foo/Bar" method="post"> … <button type="submit">Submit</button> <input name="__RequestVerificationToken" type="hidden" value="CfDJ8P4n6uxULApNkzyVaa34lxdNGtmIOsdcJ7SYtZiwwTeX9DUiCWhGIndYmXAfTqW0U3sdSpzJ-NMEoQPjxXvx6-1V-5sAonTik5oN9Yd1hej6LmP1XcwnoiQJ2dRAMyhOMIYqbduDdRI1Uxqfd0GszvI"> </form>
只需確保表單包含一些asp- *標記,它會被Razor解釋為標記助手,而不僅僅是一個常規表單元素。
- 如果出於某種原因想要從表單中省略隱藏字段,可以通過向表單元素添加屬性asp-antiforgery =“false”來實現。
- 如果您在Razor視圖中使用html助手(如@ Html.BeginForm()),您仍然可以使用@ Html.AntiForgeryToken()手動生成隱藏的輸入字段。
這就是您何生成請求標記作為表單中的隱藏字段,然后將其包含在發布的數據中。
怎么樣的cookie令牌?
Antiforgery會將請求和Cookie標記作為標記集處理。 當執行IAntiforgery方法GetAndStoreTokens(httpContext)(這是在生成表單隱藏字段時幕后發生的事情)時,它返回包含請求和cookie標記的標記集。 不僅如此,它還將確保:
- cookie標記作為僅HttpOnly添加到當前的HttpContext響應中。
- 如果已經為此請求生成了令牌集(即,已經使用相同的httpContext調用了GetAndStoreTokens),它將返回相同的令牌集,而不是再次重新生成它。 這一點很重要,因為它允許頁面的多個元素得到發布並通過驗證,就像有多個表單一樣(因為對於所有表單,cookie都是相同的,如果每個表單都有不同的標記,那么只有帶有 與cookie匹配的令牌可以通過驗證!)。
許多cookie屬性可以通過選項進行更改:
- Cookie始終設置為僅限http的cookie(所以JavaScript無法訪問它)
- 默認的Cookie名稱是為每個應用程序生成的“.AspNetCore.AntiForgery”,后面是應用程序名稱的散列。 可以通過在選項中提供CookieName來覆蓋它。
- 默認情況下沒有設置cookie域,因此瀏覽器會采用請求域。 可以通過CookieDomain選項指定一個。
- Cookie路徑默認從當前請求路徑基(通常為“/”),但可以通過CookiePath選項強制。
- 還可以在選項中啟用標志RequireSsl,這會將Cookie的Secure標志設置為true。
由於瀏覽器將隨后的請求發送cookie,無論何時發布表單,請求都將包含cookie標記(在cookie中)和請求標記(在表單字段中)。簡而言之,只要您以某種方式生成請求令牌,Cookie令牌也會生成並自動添加到響應中。
在AJAX請求中包含令牌
在上一節中,已經看到了如何在表單中使用請求令牌生成隱藏字段。 但是,在將數據作為AJAX請求的一部分發送時,如果沒有涉及任何服務器端生成的表單,怎么辦?這很大程度上取決於用於構建客戶端代碼的JavaScript框架。 Angular提供了開箱即用的CSRF支持,現在很常用,即使Antiforgery回購也包含一個示例。 我也將看看Angular-Antiforgery集成,稍后解釋如何為簡單的jQuery請求手動引入類似的方法。
CSRF - Angular案例
在Angular的情況下,使用他們的$http服務來發送AJAX請求。 如果該服務可以將標記值作為名稱為XSRF-TOKEN的cookie找到,那么該服務將自動包含名為X-XSRF-TOKEN的標頭。 所以最簡單的方法就是玩Angular想要的方式,並創建一些中間件來獲取請求令牌,並將其值存儲為XSRF-TOKEN cookie。
即使它被添加為cookie,這仍然是請求令牌而不是cookie令牌! 這可能聽起來很混亂,所以讓我試着澄清一下:
- 該應用程序將使用cookie令牌將請求令牌和另一個cookie.AspNetCore.Antiforgery.*發回給瀏覽器一個cookie XSRF-TOKEN。
- 每當Angular發送一個Ajax請求時,該請求將包含帶請求令牌的標頭X-XSRF-TOKEN和帶Cookie標記的cookie.AspNetCore.Antiforgery.*。
- Antiforgery驗證將確保這兩個令牌都是有效的並且共享相同的秘鑰等。
圖3:使用Angular的CSRF令牌
由於請求令牌的默認標頭名稱為RequestVerificationToken,因此我們需要對其進行更改並確保Antiforgery在標頭中搜索名稱為X-XSRF-TOKEN的請求令牌。 只需手動添加Antiforgery並在ConfigureServices方法中設置選項:
services.AddAntiforgery(opts => opts.HeaderName = "X-XSRF-Token");
現在需要確保生成令牌並將請求令牌包含在名為XSRF-TOKEN的cookie中,以便Angular $http服務可以讀取它並將其作為頭部包含它。
- 這不能僅僅是一個http cookie,因為Angular代碼需要讀取cookie值,以便在隨后的請求中包含它作為頭文件!
每次我們生成一個完整的html文檔時,我們都會對此感興趣,所以我們可以創建一個新的結果過濾器,如果結果是ViewResult,基本上會這樣做:
public class AngularAntiforgeryCookieResultFilter: ResultFilterAttribute { private IAntiforgery antiforgery; public AngularAntiforgeryCookieResultFilter(IAntiforgery antiforgery) { this.antiforgery = antiforgery; } public override void OnResultExecuting(ResultExecutingContext context) { if (context.Result is ViewResult) { var tokens = antiforgery.GetAndStoreTokens(context.HttpContext); context.HttpContext.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken, new CookieOptions() { HttpOnly = false }); } } }
剩下的唯一一點是將其配置為全局過濾器。 這種方式每次呈現一個完整的html文檔時,響應也會包括cookie令牌和請求令牌的cookie。 將在ConfigureServices方法中再次執行此操作:
services.AddAntiforgery(opts => opts.HeaderName = "X-XSRF-Token"); services.AddMvc(opts => { opts.Filters.AddService(typeof(AngularAntiforgeryCookieResultFilter)); }); services.AddTransient< AngularAntiforgeryCookieResultFilter >();
就是這樣,現在的Angular代碼可以使用$http服務,並且令牌將包含在請求中。 可以在GitHub中使用簡單的TODO Angular應用程序來檢查示例。
CSRF - 使用jQuery的手動案例
如果使用的是Angular以外的框架(或根本沒有框架),則處理方式標記可能會有所不同。 但是每種情況下的基本原則都是相同的,因為始終需要確保:
- 令牌是服務器端生成的。
- 請求令牌可用於客戶端JavaScript代碼。
- 客戶端JavaScript代碼將請求標記包含為標題或表單字段。
假設客戶端代碼使用jQuery發送AJAX請求。 由於已經創建了一個包含請求標記作為XSRF-TOKEN cookie的結果過濾器,並且配置了Antiforgery以在名為X-XSRF-TOKEN的標頭中查找請求標記,所以我們可以重復使用相同的方法。
需要從Cookie獲取請求令牌並將其包含在您的請求中(如果使用諸如$.ajaxSetup()之類的內容的請注意,令牌將包含在任何請求中,包括來自使用jQuery的第三方代碼的請求):
var token = readCookie('XSRF-TOKEN'); $.ajax({ url: '/api/todos', method: 'PUT', data: JSON.stringify(todo), contentType: 'application/json', headers: { 'X-XSRF-TOKEN': token }, success: onSuccess });
readCookie可以是jQuery cookies插件,也可以是自己的用於讀取cookie值(從Stack Overflow獲取)的實用工具:
function readCookie(name) { name += '='; for (var ca = document.cookie.split(/;\s*/), i = ca.length - 1; i >= 0; i--) if (!ca[i].indexOf(name)) return ca[i].replace(name, ''); }
但是只是使用這些cookie和標題名稱,因為之前為Angular設置了這種服務器。 如果沒有使用Angular,可以使用任何喜歡的名字作為cookie的請求標記(只要添加該cookie的中間件使用相同的名稱)和默認標題名稱(只要沒有指定 選項中的不同):
var token = readCookie('AnyNameYouWant'); $.ajax({ … headers: { 'RequestVerificationToken': token }, … });
事實上,不一定需要使用Cookie來將請求令牌傳播到JavaScript代碼。 只要能夠保證令牌包含在AJAX請求中,那么驗證就可以工作。 例如,可以在Razor布局中呈現一個內嵌腳本,該腳本將請求令牌添加到某個變量中,稍后,由執行jQuery AJAX請求的JavaScript代碼使用該腳本:
@*In your layout*@ @inject Microsoft.AspNetCore.Antiforgery.IAntiforgery antiforgery; @{ var antiforgeryRequestToken = antiforgery.GetAndStoreTokens(Context).RequestToken; } <script> //Render the token in a property readable from your client JavaScript app.antiforgeryToken = @Json.Serialize(antiforgeryRequestToken); </script> //In your client JavaScript $.ajax({ … headers: { 'RequestVerificationToken': app.antiforgeryToken }, … });
驗證請求源
可能已經注意到OWASP推薦了另一種保護措施,驗證請求的來源。 他們推薦的驗證方式是:
1.獲取請求源(通過查看Origin和Referer頭文件)
2.獲取目標來源(查看主機和X-Forwarded-Host標頭)
3.驗證請求和目標來源是否相同,或請求來源是否包含在白名單中的其他來源列表中。
可以實現一個IAuthorizeFilter,通過這些步驟處理所有不安全的請求(使用除GET,HEAD,TRACE和OPTIONS以外的方法的任何請求),並且在驗證失敗時將結果設置為400:
context.Result = new BadRequestResult();
編寫這樣一個過濾器並將其包含為全局過濾器不太困難。 如果想查看包含白名單額外來源選項的示例,請查看GitHub中的示例項目。
結論
安全性是任何應用的關鍵方面。 了解不同類型的攻擊以及如何防范攻擊,本身就是一門藝術。 幸運的是,通過成熟的框架簡化了整個安全過程,從而提供更多更好的安全功能。當談到特定的CSRF攻擊時,ASP.Net Core提供了保護應用程序的工具,但仍需要一些(最少的)努力才能正確配置和設置。可以在GitHub上找到一個帶有Antiforgery驗證的示例項目。