ASP.NET MVC 使用防伪造令牌来避免CSRF攻击
本文轉自這篇文章?
XSRF即在訪問B站點的時候,執行了A站點的功能。?
比如:?
A站點登錄后,可以修改用戶的郵箱(接口:/Email/Modify?email=123),修改郵箱時只驗證用戶有沒有登錄,而且登錄信息是保存在cookie中。?
用戶登錄A站點后,又打開一個窗口訪問B站點,如果這時B站點內嵌入了一條鏈接http://www.A.com/Email/Modify?email=123,當用戶點擊這條鏈接時會直接修改A站點的用戶郵箱。
對表達提交來說,要關注的就是安全問題。ASP.NET MVC 提供了探測某種攻擊類型的機制,其中一個措施就是防偽造令牌。這種令牌包含服務器端和客戶端組件,代碼會在表單中插入一個隱藏域以保護用戶特定的令牌:
@Html.AntiForgeryToken()在執行@Html.AntiForgeryToken()語句時,會在cookie中寫入一個經過加密后的數據,并在頁面中添加一個隱藏域并寫入加密后的數據(默認名稱為__RequestVerificationToken)。當執行IndexPost(前面示例)方法前,會判斷cookie中的數據與隱藏域的數據是否相等。相等則驗證通過。否則會拋出異常。(Post請求會自動把隱藏域傳遞到后臺,如果是Get請求,就需要手動把隱藏域的值傳遞到后臺)。?
待加密的數據是一個AntiForgeryToken對象。系統進行驗證時,會先把加密的數據還原成AntiForgeryToken對象,對象有一個SecurityToken屬性(用于填充隨機序列),系統主要判斷該字段的值是否相等。?
同一個會話期間,SecurityToken數據相同,所以即使開多個tab訪問相同頁面,數據驗證也會通過。?
同一個會話期間cookie中的加密數據不會改變,因為訪問頁面時,cookie會傳到后臺,后臺判斷cookie中有加密數據,就不會重新生成cookie數據。但隱藏域的值每次都不同,因為每訪問一次頁面,都會重新加密一次,雖然AntiForgeryToken對象的值相同,但通過MachineKey的Protect加密后,每次加密的值都會不同。?
AntiForgery使用MachineKey進行加密,所以如果系統使用負載均衡,就需要配置MachineKey,否則不同服務器的MachineKey不同,導致無法解密。
在執行@Html.AntiForgeryToken()語句時,會調用GetHtml方法。GetHtml方法中會調用GetFormInputElement方法,該方法會在cookie中寫入加密后的數據,并返回Html標簽代碼。該標簽代碼會寫入到頁面中.?
public static HtmlString GetHtml(){if (HttpContext.Current == null){throw new ArgumentException(WebPageResources.HttpContextUnavailable);}TagBuilder retVal = _worker.GetFormInputElement(new HttpContextWrapper(HttpContext.Current));return retVal.ToHtmlString(TagRenderMode.SelfClosing);}在GetFormInputElement方法中,首先通過GetCookieTokenNoThrow方法獲取Cookie中AntiForgeryToken對象(第一訪問頁面該對象為空)。再通過GetTokens方法獲取新的newCookieToken以及formToken(newCookieToken就是寫入cookie的token,formToken就是寫入隱藏域的token)。如果oldCookieToken不為空,那么newCookieToken就會為空,這樣就不會重新寫入cookie。所以同一個會話期間cookie值會相同。如果不為空就通過SaveCookieToken方法寫入cookie。
public TagBuilder GetFormInputElement(HttpContextBase httpContext){CheckSSLConfig(httpContext);AntiForgeryToken oldCookieToken = GetCookieTokenNoThrow(httpContext);AntiForgeryToken newCookieToken, formToken;GetTokens(httpContext, oldCookieToken, out newCookieToken, out formToken);if (newCookieToken != null){// If a new cookie was generated, persist it._tokenStore.SaveCookieToken(httpContext, newCookieToken);}if (!_config.SuppressXFrameOptionsHeader){// Adding X-Frame-Options header to prevent ClickJacking. See// http://tools.ietf.org/html/draft-ietf-websec-x-frame-options-10// for more information.httpContext.Response.AddHeader("X-Frame-Options", "SAMEORIGIN");}// <input type="hidden" name="__AntiForgeryToken" value="..." />TagBuilder retVal = new TagBuilder("input");retVal.Attributes["type"] = "hidden";retVal.Attributes["name"] = _config.FormFieldName;retVal.Attributes["value"] = _serializer.Serialize(formToken);return retVal;}SaveCookieToken方法先通過Serialize方法序列化,序列化的時候會對數據加密,再寫入cookie??
public void SaveCookieToken(HttpContextBase httpContext, AntiForgeryToken token){string serializedToken = _serializer.Serialize(token);HttpCookie newCookie = new HttpCookie(_config.CookieName, serializedToken){HttpOnly = true};// Note: don't use "newCookie.Secure = _config.RequireSSL;" since the default// value of newCookie.Secure is automatically populated from the <httpCookies>// config element.if (_config.RequireSSL){newCookie.Secure = true;}httpContext.Response.Cookies.Set(newCookie);}GetTokens方法,如果oldCookieToken不為空,就不重新生成newCookieToken。為空則通過GenerateCookieToken方法生成一個Token。再調用GenerateFormToken方法生成formToken?
private void GetTokens(HttpContextBase httpContext, AntiForgeryToken oldCookieToken, out AntiForgeryToken newCookieToken, out AntiForgeryToken formToken){newCookieToken = null;if (!_validator.IsCookieTokenValid(oldCookieToken)){// Need to make sure we're always operating with a good cookie token.oldCookieToken = newCookieToken = _validator.GenerateCookieToken();}Contract.Assert(_validator.IsCookieTokenValid(oldCookieToken));formToken = _validator.GenerateFormToken(httpContext, ExtractIdentity(httpContext), oldCookieToken);}GenerateCookieToken方法生成cookieToken,即創建一個新的AntiForgeryToken對象。AntiForgeryToken有個SecurityToken屬性,類型為BinaryBlob。BianryBlob對象會通過RNGCryptoServiceProvider實例的GetBytes方法填充強隨機序列。填充的序列就是用來驗證的隨機數。即隨機數是在創建AntiForgeryToken對象時自動生成的???
public AntiForgeryToken GenerateCookieToken(){return new AntiForgeryToken(){// SecurityToken will be populated automatically.IsSessionToken = true};}GenerateFormToken方法,就是把cookieToken的SecurityToken賦值給formToken。這樣就會使得cookieToken與formToken的SecurityToken值相等?
public AntiForgeryToken GenerateFormToken(HttpContextBase httpContext, IIdentity identity, AntiForgeryToken cookieToken){Contract.Assert(IsCookieTokenValid(cookieToken));AntiForgeryToken formToken = new AntiForgeryToken(){SecurityToken = cookieToken.SecurityToken,IsSessionToken = false};bool requireAuthenticatedUserHeuristicChecks = false;// populate Username and ClaimUidif (identity != null && identity.IsAuthenticated){if (!_config.SuppressIdentityHeuristicChecks){// If the user is authenticated and heuristic checks are not suppressed,// then Username, ClaimUid, or AdditionalData must be set.requireAuthenticatedUserHeuristicChecks = true;}formToken.ClaimUid = _claimUidExtractor.ExtractClaimUid(identity);if (formToken.ClaimUid == null){formToken.Username = identity.Name;}}// populate AdditionalDataif (_config.AdditionalDataProvider != null){formToken.AdditionalData = _config.AdditionalDataProvider.GetAdditionalData(httpContext);}if (requireAuthenticatedUserHeuristicChecks&& String.IsNullOrEmpty(formToken.Username)&& formToken.ClaimUid == null&& String.IsNullOrEmpty(formToken.AdditionalData)){// Application says user is authenticated, but we have no identifier for the user.throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture,WebPageResources.TokenValidator_AuthenticatedUserWithoutUsername, identity.GetType()));}return formToken;}生成cookieToken和formToken后就會調用Serialize方法進行序列化。序列化的時候會調用MachineKey的Protect方法進行加密。每次加密后的值都不相同。如果使用了負載均衡,一定要配置MachineKey,而不能使用系統的值?
public string Serialize(AntiForgeryToken token){Contract.Assert(token != null);using (MemoryStream stream = new MemoryStream()){using (BinaryWriter writer = new BinaryWriter(stream)){writer.Write(TokenVersion);writer.Write(token.SecurityToken.GetData());writer.Write(token.IsSessionToken);if (!token.IsSessionToken){if (token.ClaimUid != null){writer.Write(true /* isClaimsBased */);writer.Write(token.ClaimUid.GetData());}else{writer.Write(false /* isClaimsBased */);writer.Write(token.Username);}writer.Write(token.AdditionalData);}writer.Flush();return _cryptoSystem.Protect(stream.ToArray());}}}?
?
?
?
?
?
?
?
?
總結
以上是生活随笔為你收集整理的ASP.NET MVC 使用防伪造令牌来避免CSRF攻击的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C#中IEnumerable.OfTyp
- 下一篇: Windows Server 2012学