跨站请求伪造(CSRF/XSRF)
簡(jiǎn)介
CSRF(Cross-site request forgery跨站請(qǐng)求偽造,也被稱為“One Click Attack”或者Session Riding,通常縮寫為CSRF或者XSRF,是一種對(duì)網(wǎng)站的惡意利用。盡管聽起來像跨站腳本(XSS),但它與XSS非常不同,并且攻擊方式幾乎相左。XSS利用站點(diǎn)內(nèi)的信任用戶,而CSRF則通過偽裝來自受信任用戶的請(qǐng)求來利用受信任的網(wǎng)站。與XSS攻擊相比,CSRF攻擊往往不大流行(因此對(duì)其進(jìn)行防范的資源也相當(dāng)稀少)和難以防范,所以被認(rèn)為比XSS更具危險(xiǎn)性。
?
場(chǎng)景
? ? ? 某程序員大神God在某在線銀行Online Bank給他的朋友Friend轉(zhuǎn)賬。
?
轉(zhuǎn)賬后,出于好奇,大神God查看了網(wǎng)站的源文件,以及捕獲到轉(zhuǎn)賬的請(qǐng)求。
?
?
?
大神God發(fā)現(xiàn),這個(gè)網(wǎng)站沒有做防止CSRF的措施,而且他自己也有一個(gè)有一定訪問量的網(wǎng)站,于是,他計(jì)劃在自己的網(wǎng)站上內(nèi)嵌一個(gè)隱藏的Iframe偽造請(qǐng)求(每10s發(fā)送一次),來等待魚兒Fish上鉤,給自己轉(zhuǎn)賬。
網(wǎng)站源碼:
? 偽造請(qǐng)求源碼:
?
?
魚兒Fish打開了大神God的網(wǎng)站,在上面瀏覽豐富多彩的內(nèi)容。此時(shí)偽造請(qǐng)求的結(jié)果是這樣的(為了演示效果,去掉了隱藏):
因?yàn)轸~兒Fish沒有登陸,所以,偽造請(qǐng)求一直無法執(zhí)行,一直跳轉(zhuǎn)回登錄頁面。
然后魚兒Fish想起了要登錄在線銀行Online Bank查詢內(nèi)容,于是他登錄了Online Bank。
此時(shí)偽造請(qǐng)求的結(jié)果是這樣的(為了演示效果,去掉了隱藏):
?
?
魚兒Fish每10秒會(huì)給大神God轉(zhuǎn)賬100元。
防止CSRF
CSRF能成功是因?yàn)橥粋€(gè)瀏覽器會(huì)共享Cookies,也就是說,通過權(quán)限認(rèn)證和驗(yàn)證是無法防止CSRF的。那么應(yīng)該怎樣防止CSRF呢?其實(shí)防止CSRF的方法很簡(jiǎn)單,只要確保請(qǐng)求是自己的站點(diǎn)發(fā)出的就可以了。那怎么確保請(qǐng)求是發(fā)自于自己的站點(diǎn)呢?ASP.NET以Token的形式來判斷請(qǐng)求。
我們需要在我們的頁面生成一個(gè)Token,發(fā)請(qǐng)求的時(shí)候把Token帶上。處理請(qǐng)求的時(shí)候需要驗(yàn)證Cookies+Token。
?
此時(shí)偽造請(qǐng)求的結(jié)果是這樣的(為了演示效果,去掉了隱藏):
?
$.ajax
如果我的請(qǐng)求不是通過Form提交,而是通過Ajax來提交,會(huì)怎樣呢?結(jié)果是驗(yàn)證不通過。
為什么會(huì)這樣子?我們回頭看看加了@Html.AntiForgeryToken()后頁面和請(qǐng)求的變化。
1. 頁面多了一個(gè)隱藏域,name為__RequestVerificationToken。
2. 請(qǐng)求中也多了一個(gè)字段__RequestVerificationToken。
?
原來要加這么個(gè)字段,我也加一個(gè)不就可以了!
啊!為什么還是不行...逼我放大招,研究源碼去!
噢!原來token要從Form里面取。但是ajax中,Form里面并沒有東西。那token怎么辦呢?我把token放到碗里,不對(duì),是放到header里。
js代碼:
$(function () { ? ? ? ? ? ?var token = $('@Html.AntiForgeryToken()').val();$('#btnSubmit').click(function () { ? ? ? ? ? ? ? ?var targetUser = $('#TargetUser').val(); ? ? ? ? ? ? ? ?var amount = $('#Amount').val(); ? ? ? ? ? ? ? ?var data = { 'targetUser': targetUser, 'amount': amount }; ? ? ? ? ? ? ? ?return $.ajax({url: '@Url.Action("Transfer2", "Home")',type: 'POST',data: JSON.stringify(data),contentType: 'application/json',dataType: 'json',traditional: 'true',beforeSend: function (xhr) {xhr.setRequestHeader('__RequestVerificationToken', token);},success:function() {window.location = '@Url.Action("Index", "Home")';}});});});?
在服務(wù)端,參考ValidateAntiForgeryTokenAttribute,編寫一個(gè)AjaxValidateAntiForgeryTokenAttribute:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] ? ?public class AjaxValidateAntiForgeryTokenAttribute : FilterAttribute, IAuthorizationFilter{ ? ? public void OnAuthorization(AuthorizationContext filterContext){ ? ? ? ?
? ? ? ? ? ?if (filterContext == null){ ? ? ? ? ? ? ?
? ? ? ? ? ? ? ?throw new ArgumentNullException("filterContext");} ? ? ?
?? ? ?var request = filterContext.HttpContext.Request; ? ? ? ? ? ?var antiForgeryCookie = request.Cookies[AntiForgeryConfig.CookieName]; ? ? ? ? ?
? ? ??var cookieValue = antiForgeryCookie != null ? antiForgeryCookie.Value : null; ? ? ? ? ?
? ? ??var formToken = request.Headers["__RequestVerificationToken"];AntiForgery.Validate(cookieValue, formToken);}}
?
然后調(diào)用時(shí)把ValidateAntiForgeryToken替換成AjaxValidateAntiForgeryToken。
大功告成,好有成就感!
?
全局處理
如果所有的操作請(qǐng)求都要加一個(gè)ValidateAntiForgeryToken或者AjaxValidateAntiForgeryToken,不是挺麻煩嗎?可以在某個(gè)地方統(tǒng)一處理嗎?答案是闊儀的。
ValidateAntiForgeryTokenAttribute繼承IAuthorizationFilter,那就在AuthorizeAttribute里做統(tǒng)一處理吧。
ExtendedAuthorizeAttribute:
public class ExtendedAuthorizeAttribute : AuthorizeAttribute{ ? ? ? ??public override void OnAuthorization(AuthorizationContext filterContext){PreventCsrf(filterContext); ?
? ? ? ? ? ?base.OnAuthorization(filterContext);GenerateUserContext(filterContext);} ? ? ?
? ? ? ??/// <summary>/// http://www.asp.net/mvc/overview/security/xsrfcsrf-prevention-in-aspnet-mvc-and-web-pages/// </summary>private static void PreventCsrf(AuthorizationContext filterContext){ ? ? ? ? ?
? ? ? ?var request = filterContext.HttpContext.Request; ? ? ? ? ? ?if (request.HttpMethod.ToUpper() != "POST"){ ? ? ? ? ? ?
? ? ? ? ? ? ?? ?return;} ? ? ? ? ?
? ? ? ? ? ?var allowAnonymous = HasAttribute(filterContext, typeof(AllowAnonymousAttribute)); ?
? ? ? ? ? ?if (allowAnonymous){ ? ? ? ? ? ? ?
? ? ? ? ? ? ? ?return;} ? ? ? ? ?
? ? ? ? ? ?var bypass = HasAttribute(filterContext, typeof(BypassCsrfValidationAttribute)); ?
? ? ? ? ? ?if (bypass){ ? ? ? ? ? ? ? ?return;} ? ? ? ? ?
? ? ? ? ? ?if (filterContext.HttpContext.Request.IsAjaxRequest()){ ? ? ? ? ?
? ? ? ? ? ? ? var antiForgeryCookie = request.Cookies[AntiForgeryConfig.CookieName]; ? ? ? ?
?? ? ? ?var cookieValue = antiForgeryCookie != null ? antiForgeryCookie.Value : null; ? ? ? ? ? ? ?
? ? ? ? ? ?var formToken = request.Headers["__RequestVerificationToken"];AntiForgery.Validate(cookieValue, formToken);} ? ?
? ? ? ? ? ?else{AntiForgery.Validate();}} ? ? ? ?
private static bool HasAttribute(AuthorizationContext filterContext, Type attributeType){ ? ? ? ? ?
??return filterContext.ActionDescriptor.IsDefined(attributeType, true) ||filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(attributeType, true);} ? ? ? ?
private static void GenerateUserContext(AuthorizationContext filterContext){ ? ? ? ? ?
??var formsIdentity = filterContext.HttpContext.User.Identity as FormsIdentity; ? ?
?? ? ? ?if (formsIdentity == null || string.IsNullOrWhiteSpace(formsIdentity.Name)){UserContext.Current = null; ? ? ? ? ? ? ? ?return;}UserContext.Current = new WebUserContext(formsIdentity.Name);}}
?
然后在FilterConfig注冊(cè)一下。
FAQ:
1.?BypassCsrfValidationAttribute是什么鬼?不是有個(gè)AllowAnonymousAttribute嗎?
如果有些操作你不需要做CSRF的處理,比如附件上傳,你可以在對(duì)應(yīng)的Controller或Action上添加BypassCsrfValidationAttribute。
AllowAnonymousAttribute不僅會(huì)繞過CSRF的處理,還會(huì)繞過認(rèn)證和驗(yàn)證。BypassCsrfValidationAttribute繞過CSRF但不繞過認(rèn)證和驗(yàn)證,
也就是BypassCsrfValidationAttribute作用于那些登錄或授權(quán)后的Action。
?
2. 為什么只處理POST請(qǐng)求?
我開發(fā)的時(shí)候有一個(gè)原則,查詢都用GET,操作用POST,而對(duì)于查詢的請(qǐng)求沒有必要做CSRF的處理。大家可以按自己的需要去安排!
3. 我做了全局處理,然后還在Controller或Action上加了ValidateAntiForgeryToken或者AjaxValidateAntiForgeryToken,會(huì)沖突嗎?
不會(huì)沖突,只是驗(yàn)證會(huì)做兩次。
?
源碼下載
為了方便使用,我沒有使用任何數(shù)據(jù)庫,而是用了一個(gè)文件來存儲(chǔ)數(shù)據(jù)。代碼下載后可以直接運(yùn)行,無需配置。
下載地址:https://github.com/ErikXu/CSRF
原文地址:http://www.cnblogs.com/Erik_Xu/p/5481441.html
.NET社區(qū)新聞,深度好文,微信中搜索dotNET跨平臺(tái)或掃描二維碼關(guān)注
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的跨站请求伪造(CSRF/XSRF)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: .NET 框架兼容性简介
- 下一篇: ASP.NET Core Docker部