ASP.NET MVC 和 ASP.NET Web Pages 中的 XSRF/CSRF 防護
作者: Rick Anderson
跨網站偽造要求 (也稱為 XSRF 或 CSRF) 是針對 Web 裝載應用程式的攻擊,惡意網站可能會影響用戶端瀏覽器與該瀏覽器信任的網站之間的互動。 這些攻擊是可行的,因為網頁瀏覽器會隨著每個要求自動傳送驗證權杖至網站。 ASP.NET 的 Forms Authentication 票證即是驗證 Cookie 的標準範例。 不過,使用任何持續性驗證機制的網站 (例如 Windows 驗證、基本等) 都可以由這些攻擊鎖定。
XSRF 攻擊與網路釣魚攻擊不同。 網路釣魚攻擊需要與受害者互動。 在網路釣魚攻擊中,惡意網站會模擬目標網站,而犧牲者會誤判為攻擊者提供敏感性資訊。 XSRF 攻擊則通常不需要與受害者互動。 相反地,攻擊者依賴瀏覽器自動將所有相關 Cookie 傳送至目的地網站。
如需詳細資訊,請參閱 Open Web Application Security Project (OWASP) XSRF。
攻擊的結構
若要逐步執行 XSRF 攻擊,請考慮想要執行一些線上銀行交易的使用者。 此使用者會先流覽 WoodgroveBank.com 並登入,此時回應標頭會包含她的驗證 Cookie:
HTTP/1.1 200 OK
Date: Mon, 18 Jun 2012 21:22:33 GMT
X-AspNet-Version: 4.0.30319
Set-Cookie: .ASPXAUTH={authentication-token}; path=/; secure; HttpOnly;
{ Cache-Control, Content-Type, Location, Server and other keys/values not listed. }
由於驗證 Cookie 是會話 Cookie,因此瀏覽器會在瀏覽器進程結束時自動清除。 不過,在該時間之前,瀏覽器會自動將 Cookie 包含在每個要求 WoodgroveBank.com。 使用者現在想要將 $1000 美元轉移給另一個帳戶,因此她填寫銀行網站上的表單,而瀏覽器會向伺服器提出此要求:
POST /DoTransfer HTTP/1.1
Host: WoodgroveBank.com
Content-Type: application/x-www-form-urlencoded
Cookie: .ASPXAUTH={authentication-token}
toAcct=12345&amount=1,000.00
由於此作業在起始貨幣交易) (有副作用,因此銀行網站已選擇要求 HTTP POST 才能起始此作業。 伺服器會從要求讀取驗證權杖、查閱目前使用者的帳戶號碼、確認有足夠的資金存在,然後將交易起始至目的地帳戶。
她的線上銀行完成,使用者會離開銀行網站,並流覽網路上的其他位置。 其中一個網站 - fabrikam.com – 包含下列標記內嵌在 iframe > 中的 < 頁面上:
<form id="theForm" action="https://WoodgroveBank.com/DoTransfer" method="post">
<input type="hidden" name="toAcct" value="67890" />
<input type="hidden" name="amount" value="250.00" />
</form>
<script type="text/javascript">
document.getElementById('theForm').submit();
</script>
這會導致瀏覽器提出此要求:
POST /DoTransfer HTTP/1.1
Host: WoodgroveBank.com
Content-Type: application/x-www-form-urlencoded
Cookie: .ASPXAUTH={authentication-token}
toAcct=67890&amount=250.00
攻擊者正惡意探索使用者可能仍有目標網站的有效驗證權杖,而且她使用一小部分的 JAVAscript 程式碼片段,讓瀏覽器自動將 HTTP POST 設為目標網站。 如果驗證權杖仍然有效,銀行網站會將 $250 元的轉移起始到攻擊者選擇的帳戶。
不正確風險降低
請注意,在上述案例中,WoodgroveBank.com 是透過 SSL 存取,而且只有 SSL 的驗證 Cookie 不足以阻止攻擊。 只要這些 Cookie 與預定目標的 URI (配置 一致,攻擊者就能夠在其 < 表單 > 元素中指定 HTTPs) ) ,而且只要這些 Cookie 與預定目標的 URI 配置一致,瀏覽器就會繼續將未到期的 Cookie 傳送至目標網站。
其中一個可能認為使用者不應該造訪不受信任的網站,因為只流覽信任的網站有助於保持安全線上。 這有一些事實,但不幸的是,這項建議不一定可行。 或許使用者「信任」本機新聞網站 ConsolidatedMessenger.com,並改為流覽該網站,但該網站有 XSS 弱點,可讓攻擊者插入 fabrikam.com 上執行的相同程式碼片段。
您可以確認傳入要求具有參考網域的 參考者標頭 。 這將會停止從協力廠商網域不小心提交的要求。 不過,有些人員基於隱私權考慮停用瀏覽器的「參考者」標頭,如果犧牲者已安裝某些不安全的軟體,攻擊者有時可能會詐騙該標頭。 確認 Referer 標頭 並未被視為防止 XSRF 攻擊的安全方法。
Web Stack 執行時間 XSRF 防護功能
ASP.NET Web Stack 執行時間會使用 同步器權杖模式 的變體來防禦 XSRF 攻擊。 同步器權杖模式的一般形式是,除了驗證權杖 () 之外,還會提交兩個反 XSRF 權杖給伺服器,除了驗證權杖) :一個權杖做為 Cookie,另一個則提交為表單值。 攻擊者無法確定或預測 ASP.NET 執行時間所產生的權杖值。 提交權杖時,伺服器只有在這兩個權杖都通過比較檢查時,才會允許要求繼續進行。
XSRF 要求驗證 會話權杖 會儲存為 HTTP Cookie,目前在其承載中包含下列資訊:
- 安全性權杖,由隨機的 128 位識別碼組成。
下圖顯示以 Internet Explorer F12 開發人員工具顯示的 XSRF 要求驗證會話權杖: (請注意,這是目前的實作,而且可能也會變更。)
欄位權杖會儲存為 , <input type="hidden" />
並在其承載中包含下列資訊:
- 如果已驗證) ,則登入的使用者名稱 (。
- IAntiForgeryAdditionalDataProvider所提供的任何其他資料。
反 XSRF 權杖的承載會經過加密和簽署,因此在使用工具來檢查權杖時,您無法檢視使用者名稱。 當 Web 應用程式以 ASP.NET 4.0 為目標時, MachineKey.Encode 常式會提供密碼編譯服務。 當 Web 應用程式以 ASP.NET 4.5 或更高版本為目標時, MachineKey.Protect 常式會提供密碼編譯服務,以提供更佳的效能、擴充性和安全性。 如需詳細資訊,請參閱下列部落格文章:
產生權杖
若要產生反 XSRF 權杖,請從 MVC 檢視或 @AntiForgery.GetHtml() Razor 頁面呼叫@Html.AntiForgeryToken方法。 執行時間接著會執行下列步驟:
- 如果目前的 HTTP 要求已經包含反 XSRF 會話權杖, (反 XSRF Cookie __RequestVerificationToken) ,則會從中擷取安全性權杖。 如果 HTTP 要求未包含反 XSRF 會話權杖,或擷取安全性權杖失敗,則會產生新的隨機反 XSRF 權杖。
- 使用上述步驟 (1) 的安全性權杖和目前登入使用者的身分識別,產生反 XSRF 欄位權杖。 (如需判斷使用者身分識別的詳細資訊,請參閱下方 具有特殊支援 案例一節。) 此外,如果已設定 IAntiForgeryAdditionalDataProvider ,執行時間會呼叫其 GetAdditionalData 方法,並在欄位權杖中包含傳回的字串。 (如需詳細資訊,請參閱設定 和擴充性 一節。)
- 如果在步驟 1) (1 中產生新的反 XSRF 權杖,則會建立新的會話權杖來包含它,並將新增至輸出 HTTP Cookies 集合。 步驟 (2) 的欄位標記會包裝在 元素中
<input type="hidden" />
,而這個 HTML 標籤會是 或AntiForgery.GetHtml()
的Html.AntiForgeryToken()
傳回值。
驗證權杖
為了驗證傳入的反 XSRF 權杖,開發人員會在 MVC 動作或控制器上加入 ValidateAntiForgeryToken 屬性,或從 Razor 頁面呼叫 @AntiForgery.Validate()
。 執行時間將會執行下列步驟:
- 會讀取傳入會話權杖和欄位權杖,並從每個權杖擷取反 XSRF 權杖。 在產生常式中,反 XSRF 權杖的每個步驟必須相同 (2) 。
- 如果目前的使用者經過驗證,其使用者名稱會與儲存在欄位權杖中的使用者名稱進行比較。 使用者名稱必須相符。
- 如果已設定 IAntiForgeryAdditionalDataProvider ,執行時間會呼叫其 ValidateAdditionalData 方法。 方法必須傳回布林值 true。
如果驗證成功,則允許要求繼續進行。 如果驗證失敗,架構會擲回 HttpAntiForgeryException。
失敗狀況
從 ASP.NET Web Stack 執行時間 v2 開始,驗證期間擲回的任何 HttpAntiForgeryException 都會包含發生錯誤的詳細資訊。 目前定義的失敗狀況如下:
- 要求中沒有會話權杖或表單權杖。
- 會話權杖或表單權杖無法讀取。 這可能是因為伺服器陣列執行不相符版本的伺服器陣列,ASP.NET Web Stack 執行時間或伺服器陣列,其中 < Web.config中的 machineKey > 元素在電腦之間有所不同。 您可以使用 Fiddler 之類的工具,藉由竄改任何一個反 XSRF 權杖來強制此例外狀況。
- 已交換會話權杖和欄位權杖。
- 會話權杖和欄位權杖包含不相符的安全性權杖。
- 內嵌在欄位權杖內的使用者名稱與目前登入的使用者名稱不符。
- IAntiForgeryAdditionalDataProvider.ValidateAdditionalData方法傳回false。
反 XSRF 設施也可以在權杖產生或驗證期間執行額外的檢查,在這些檢查期間失敗可能會導致擲回例外狀況。 如需詳細資訊,請參閱WIF / ACS / 宣告型驗證和組態和擴充性小節。
具有特殊支援的案例
匿名驗證
反 XSRF 系統包含匿名使用者的特殊支援,其中「anonymous」 定義為 IIdentity.IsAuthenticated 屬性傳回 false的使用者。 案例包括提供 XSRF 保護給登入頁面, (使用者驗證) 和自訂驗證配置,其中應用程式會使用 IIdentity 以外的機制來識別使用者。
若要支援這些案例,請記得會話和欄位權杖會由安全性權杖聯結,這是 128 位隨機產生的不透明識別碼。 此安全性權杖可用來在流覽網站時追蹤個別使用者的會話,因此它有效地提供匿名識別碼的目的。 空字串用於取代上述產生和驗證常式的使用者名稱。
WIF / ACS / 宣告型驗證
一般而言,.NET Framework內建的IIdentity類別具有屬性,IIdentity.Name足以唯一識別特定應用程式內的特定使用者。 例如, FormsIdentity.Name 傳回儲存在成員資格資料庫中的使用者名稱, (根據該資料庫) 的所有應用程式而言是唯一的, WindowsIdentity.Name 傳回使用者的網域限定身分識別等等。 這些系統不僅提供驗證;他們也會 識別 應用程式的使用者。
另一方面,宣告型驗證不一定需要識別特定使用者。 相反地, ClaimsPrincipal 和 ClaimsIdentity 類型會與一組 宣告 實例相關聯,其中個別宣告可能是「年齡為 18+ 年」,或「是系統管理員」至任何其他專案。 由於使用者不一定已識別,執行時間無法使用 ClaimsIdentity.Name 屬性做為此特定使用者的唯一識別碼。 小組已看到真實世界的範例,其中 ClaimsIdentity.Name 傳回 null、傳回易記 (顯示) 名稱,或傳回不適合用來作為使用者唯一識別碼的字串。
許多使用宣告式驗證的部署都是使用Azure 存取控制 Service ( ACS) 。 ACS 可讓開發人員 (設定個別 識別提供者 ,例如 ADFS、Microsoft 帳戶提供者、如 Yahoo!等 OpenID 提供者等) ,而識別提供者會傳回 名稱識別碼。 這些名稱識別碼可能包含個人識別資訊 (PII) ,例如電子郵件地址,或是匿名化,例如私人個人識別碼 () 。 不論 Tuple (識別提供者,名稱識別碼) 在流覽網站時就足以作為特定使用者的適當追蹤權杖,因此 ASP.NET Web Stack 執行時間可以在產生和驗證反 XSRF 欄位權杖時,使用 Tuple 取代使用者名稱。 識別提供者和名稱識別碼的特定 URI 為 :
https://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier
如需詳細資訊, (請參閱此 ACS 檔頁面 。)
產生或驗證權杖時,ASP.NET Web Stack 執行時間會在執行時間嘗試系結至類型:
Microsoft.IdentityModel.Claims.IClaimsIdentity, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
適用于 WIF SDK.) 的 (System.Security.Claims.ClaimsIdentity
適用于 .NET 4.5) 的 (。
如果這些類型存在,而且目前使用者的 IIIIdentity 會實作或子類別其中一種類型,則反 XSRF 設施會使用 (識別提供者、名稱識別碼) Tuple 來取代產生和驗證權杖時的使用者名稱。 如果沒有這類 Tuple,要求將會失敗,並顯示錯誤,說明開發人員如何設定反 XSRF 系統,以瞭解使用中的特定宣告式驗證機制。 如需詳細資訊,請參閱 設定和擴充性 一節。
OAuth / OpenID 驗證
最後,反 XSRF 設施對使用 OAuth 或 OpenID 驗證的應用程式有特殊支援。 此支援是以啟發學習法為基礎:如果目前的 IIdentity.Name 以 HTTP:// 或 HTTPs:// 開始,則會使用序數比較子來完成使用者名稱比較,而不是預設 OrdinalIgnoreCase 比較子。
組態和擴充性
有時候,開發人員可能會想要更嚴格地控制反 XSRF 產生和驗證行為。 例如,或許 MVC 和網頁協助程式的預設行為會自動將 HTTP Cookie 新增至回應,而開發人員可能想要將權杖保存在其他地方。 有兩個 API 可協助執行此動作:
AntiForgery.GetTokens(string oldCookieToken, out string newCookieToken, out string formToken);
AntiForgery.Validate(string cookieToken, string formToken);
GetTokens方法會接受輸入現有的 XSRF 要求驗證會話權杖, (可能是 null) ,並產生作為輸出新的 XSRF 要求驗證會話權杖和欄位權杖。 標記只是沒有裝飾的不透明字串;例如,formToken值不會包裝在輸入 > 標記中 < 。 newCookieToken值可能是 null;如果發生這種情況,則oldCookieToken值仍然有效,而且不需要設定任何新的回應 Cookie。 GetTokens的呼叫端負責保存任何必要的回應 Cookie 或產生任何必要的標記;GetTokens方法本身不會改變回應做為副作用。 Validate方法會採用傳入的會話和欄位權杖,並對其執行上述驗證邏輯。
AntiForgeryConfig
開發人員可以從 Application_Start 設定反 XSRF 系統。 組態是以程式設計方式進行。 靜態 AntiForgeryConfig 類型的屬性如下所述。 大部分使用宣告的使用者都會想要設定 UniqueClaimTypeIdentifier 屬性。
屬性 | 描述 |
---|---|
AdditionalDataProvider | IAntiForgeryAdditionalDataProvider,可在權杖產生期間提供額外的資料,並在權杖驗證期間取用其他資料。 預設值為 null。 如需詳細資訊,請參閱 IAntiForgeryAdditionalDataProvider 一節。 |
CookieName | 字串,提供用來儲存反 XSRF 會話權杖的 HTTP Cookie 名稱。 如果未設定此值,則會根據應用程式的已部署虛擬路徑自動產生名稱。 預設值為 null。 |
RequireSsl | 布林值,指定是否必須透過 SSL 保護的通道提交反 XSRF 權杖。 如果此值 為 true,則任何自動產生的 Cookie 都會設定「安全」旗標,而且如果從未透過 SSL 提交的要求內呼叫,則會擲回反 XSRF API。 預設值為 false。 |
SuppressIdentityHeuristicChecks | 布林值,指出反 XSRF 系統是否應該停用其宣告型身分識別的支援。 如果此值為 true,系統會假設IIdentity.Name適合作為唯一每個使用者識別碼,而且不會嘗試特殊案例IClaimsIdentity 或 ClClaimsIdentity,如WIF / ACS / 宣告式驗證一節中所述。 預設值是 false 。 |
UniqueClaimTypeIdentifier | 字串,指出哪個宣告類型適合作為唯一的每個使用者識別碼使用。 如果已設定此值,且目前的 IIdentity 是以宣告為基礎,則系統會嘗試擷取 UniqueClaimTypeIdentifier所指定類型的宣告,而對應的值將會用來取代使用者的使用者名稱產生欄位權杖時。 如果找不到宣告類型,系統將會失敗要求。 預設值為 null,表示系統應該使用 (識別提供者、名稱識別碼) 元組,如先前所述取代使用者的使用者名稱。 |
IAntiForgeryAdditionalDataProvider
IAntiForgeryAdditionalDataProvider類型可讓開發人員藉由往返每個權杖中的其他資料來擴充反 XSRF 系統的行為。 每次產生欄位權杖時,都會呼叫 GetAdditionalData 方法,而傳回值會內嵌在產生的權杖中。 實作者可以從這個方法傳回時間戳記、nonce 或任何其他值。
同樣地,每次驗證欄位權杖時都會呼叫 ValidateAdditionalData 方法,而內嵌在權杖中的「其他資料」字串會傳遞至 方法。 驗證常式可以針對建立權杖的時間檢查目前時間,) 、nonce 檢查常式或任何其他所需的邏輯,以實作逾時 (。
設計決策和安全性考慮
只有在嘗試保護匿名/未經驗證的使用者免于 XSRF 攻擊時,才需要連結會話和欄位權杖的安全性權杖。 驗證使用者時,驗證權杖本身 (可能以 Cookie 的形式提交,) 可作為同步器權杖配對的一半。 不過,有一些有效的案例可用來保護未經驗證的使用者所叫用的登入頁面,而反 XSRF 邏輯則一律產生和驗證安全性權杖,甚至針對已驗證的使用者也變得更簡單。 它也會在攻擊者遭到入侵時提供一些額外的保護,因為設定或猜測會話權杖會是攻擊者要克服的另一個障礙。
當多個應用程式裝載于單一網域時,開發人員應該小心。 例如,即使 example1.cloudapp.net 和 example2.cloudapp.net 是不同的主機, *.cloudapp.net 網域下的所有主機之間還是有隱含的信任關係。 此隱含信任關係 可讓可能不受信任的主機影響彼此的 Cookie , (控管 AJAX 要求的相同原始原則不一定適用于 HTTP Cookie) 。 ASP.NET Web Stack 執行時間提供一些風險降低,因為使用者名稱內嵌在欄位權杖中,因此即使惡意子域能夠覆寫會話權杖,它也無法為使用者產生有效的欄位權杖。 不過,在這類環境中裝載時,內建的反 XSRF 常式仍然無法防禦會話攔截或登入 XSRF。
反 XSRF 常式目前不會防禦 點擊攔截。 想要自行防禦點擊攔截的應用程式,可以透過傳送 X-Frame-Options:SAMEORIGIN 標頭與每個回應,輕鬆地這麼做。 所有最近的瀏覽器都支援此標頭。 如需詳細資訊,請參閱 IE 部落格、 SDL 部落格和 OWASP。 ASP.NET Web Stack 執行時間可能會在未來版本中讓 MVC 和網頁反 XSRF 協助程式自動設定此標頭,以便自動保護應用程式免于遭受此攻擊。
Web 開發人員應該繼續確保其網站不會遭受 XSS 攻擊。 XSS 攻擊非常強大,而且成功的惡意探索也會破壞對 XSRF 攻擊的 ASP.NET Web Stack 執行時間防禦。
通知
@LeviBroderick,撰寫大部分 ASP.NET 安全性程式碼的人員會大量撰寫此資訊。