mvc 当中 [ValidateAntiForgeryToken] 的作用及用法
一.CSRF是什么?
CSRF(Cross-site request forgery
),中文名称:跨站请求伪造,也被称为:one click attack/session riding
,缩写为:CSRF/XSRF
。
二.CSRF
可以做什么?
你这可以这么理解CSRF攻击:攻击者盗用了你的身份,以你的名义发送恶意请求。CSRF能够做的事情包括:以你名义发送邮件,发消息,盗取你的账号,甚至于购买商品,虚拟货币转账…造成的问题包括:个人隐私泄露以及财产安全。
三.CSRF漏洞现状
CSRF这种攻击方式在2000年已经被国外的安全人员提出,但在国内,直到06年才开始被关注,08年,国内外的多个大型社区和交互网站分别 爆出CSRF漏洞,如:NYTimes.com(纽约时报)、Metafilter(一个大型的BLOG网站),YouTube和百度HI…而 现在,互联网上的许多站点仍对此毫无防备,以至于安全业界称CSRF为“沉睡的巨人”。
四.CSRF的原理 从上图可以看出,要完成一次CSRF攻击,受害者必须依次完成两个步骤:
登录受信任网站A,并在本地生成
Cookie
。在不登出A的情况下,访问危险网站B。
看到这里,你也许会说:“如果我不满足以上两个条件中的一个,我就不会受到CSRF的攻击”。是的,确实如此,但你不能保证以下情况不会发生:
- 你不能保证你登录了一个网站后,不再打开一个tab页面并访问另外的网站。
- 你不能保证你关闭浏览器了后,你本地的
Cookie
立刻过期,你上次的会话已经结束。(事实上,关闭浏览器不能结束一个会话,但大多数人都会错误的认为关闭浏览器就等于退出登录/结束会话了…) - 上图中所谓的攻击网站,可能是一个存在其他漏洞的可信任的经常被人访问的网站。
以上内容转自:http://www.cnblogs.com/hyddd/archive/2009/04/09/1432744.html
具体步骤:
1、在Html表单里面使用了@Html.AntiForgeryToken()
就可以阻止CSRF
攻击。
2、相应的我们要在Controller
中也要加入[ValidateAntiForgeryToken]
过滤特性。该特性表示检测服务器请求是否被篡改。注意:该特性只能用于post
请求,get
请求无效。
3、至于JS,我们的项目中引用的是<script src="@Url.Content("~/Content/js/jqueryToken-1.4.2.js")" type="text/javascript"></script>
在JS时要使用: $.ajaxAntiForgery
才行,
如:
$.ajaxAntiForgery({type: "post",data: { GroupName: $("#GroupName").val(), GroupPhones: $("#GroupPhones").val() },dataType: "json",url: "/Event/Mass/AddGroup",success: function (data) {if (data) {alert("添加成功 ");$.unblockUI();}else {alert("添加失败 ");}}})
注:对数据进行增删改时要防止csrf攻击!
ASP.NET MVC过滤器中权限过滤器ValidateAntiForgeryToken
的用法(Post-Only
)
用途:防止CSRF(跨网站请求伪造)。
用法:
在View->Form
表单中:<%:Html.AntiForgeryToken()%>
在Controller->Action
动作上:[ValidateAntiForgeryToken]
原理:
1、<%:Html.AntiForgeryToken()%>
这个方法会生成一个隐藏域:<input name="__RequestVerificationToken" type="hidden" value="7FTM...sdLr1" />
并且会将一个以"__RequestVerificationToken
“为KEY
的COOKIE
给控制层。
2、[ValidateAntiForgeryToken]
,根据传过来的令牌进行对比,如果相同,则允许访问,如果不同则拒绝访问。
关键:ValidateAntiForgeryToken
只针对POST
请求。
换句话说,[ValidateAntiForgeryToken]
必须和[HttpPost]
同时加在一个ACTION
上才可以正常使用。
这其中的原理我也没想明白,等下次好好把MVC的源代码看看。
不过我这么说是有根据的,我写了一些案例做了测试。
案例:
1、在一个ACTION
的GET
和POST
方式分别加了[ValidateAntiForgeryToken]
特性
Action:
2、用一个测试页面以POST
方式去请求ACTION
,结果是成功的。并且,隐藏域的值和COOKIE
都是可以拿到的。
测试Post的页面:
3、用一个测试页面以GET
方式去请求ACTION
,报错。
测试Get的页面:
推荐使用方式:
1、Post-Only
:大概思想是,拒绝所有的GET
,只允许自己的POST
。(安全,但不灵活)
2、GET
只做显示,对所有的GET
开放;POST
做修改,对外界关闭,对自己开放。(灵活,但不够安全)
国外有个人说,其实这个过滤器本身就不安全,他如是说,所有的REQUEST
都是可以伪造的。
初探CSRF
在ASP.NET Core中的处理方式
前言
前几天,有个朋友问我关于AntiForgeryToken
问题,由于对这一块的理解也并不深入,所以就去研究了一番,梳理了一下。
在梳理之前,还需要简单了解一下背景知识。
AntiForgeryToken
可以说是处理/预防CSRF的一种处理方案。
那么什么是CSRF呢?
CSRF(Cross-site request forgery)
是跨站请求伪造,也被称为One Click Attack
或者Session Riding
,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用。
简单理解的话就是:有人盗用了你的身份,并且用你的名义发送恶意请求。
最近几年,CSRF处于不温不火的地位,但是还是要对这个小心防范!
更加详细的内容可以参考维基百科:
下面从使用的角度来分析一下CSRF
在 ASP.NET Core
中的处理,个人认为主要有下面两大块
- 视图层面
- 控制器层面
视图层面
用法
@Html.AntiForgeryToken()
在视图层面的用法相对比较简单,用的还是HtmlHelper
的那一套东西。在Form
表单中加上这一句就可以了。
原理浅析
当在表单中添加了上面的代码后,页面会生成一个隐藏域,隐藏域的值是一个生成的token
(防伪标识),类似下面的例子
<input name="__RequestVerificationToken" type="hidden" value="CfDJ8FBn4LzSYglJpE6Q0fWvZ8WDMTgwK49lDU1XGuP5-5j4JlSCML_IDOO3XDL5EOyI_mS2Ux7lLSfI7ASQnIIxo2ScEJvnABf9v51TUZl_iM2S63zuiPK4lcXRPa_KUUDbK-LS4HD16pJusFRppj-dEGc" />
其中的name="__RequestVerificationToken"
是定义的一个const
变量,value=XXXXX
是根据一堆东西进行编码,并对编码后的内容进行简单处理的结果,具体的实现可以参见
生成上面隐藏域的代码在AntiforgeryExtensions
这个文件里面,github上的源码文件:
其中重点的方法如下:
public void WriteTo(TextWriter writer, HtmlEncoder encoder)
{writer.Write("<input name=\"");encoder.Encode(writer, _fieldName);writer.Write("\" type=\"hidden\" value=\"");encoder.Encode(writer, _requestToken);writer.Write("\" />");
}
相当的清晰明了!
控制器层面
用法
[ValidateAntiForgeryToken]
[AutoValidateAntiforgeryToken]
[IgnoreAntiforgeryToken]
这三个都是可以基于类或方法的,所以我们只要在某个控制器或者是在某个Action
上面加上这些Attribute
就可以了。
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
原理浅析
本质是Filter
(过滤器),验证上面隐藏域的value
过滤器实现:ValidateAntiforgeryTokenAuthorizationFilter
和AutoValidateAntiforgeryTokenAuthorizationFilter
其中 AutoValidateAntiforgeryTokenAuthorizationFilter
是继承了ValidateAntiforgeryTokenAuthorizationFilter
,只重写了其中的ShouldValidate
方法。
下面贴出ValidateAntiforgeryTokenAuthorizationFilter
的核心方法:
public class ValidateAntiforgeryTokenAuthorizationFilter : IAsyncAuthorizationFilter, IAntiforgeryPolicy
{public async Task OnAuthorizationAsync(AuthorizationFilterContext context){if (context == null){throw new ArgumentNullException(nameof(context));}if (IsClosestAntiforgeryPolicy(context.Filters) && ShouldValidate(context)){try{await _antiforgery.ValidateRequestAsync(context.HttpContext);}catch (AntiforgeryValidationException exception){_logger.AntiforgeryTokenInvalid(exception.Message, exception);context.Result = new BadRequestResult();}}}
}
完整实现可参见github源码:
当然这里的过滤器只是一个入口,相关的验证并不是在这里实现的。而是在Antiforgery
这个项目上,其实说这个模块可能会更贴切一些。
由于是面向接口的编程,所以要知道具体的实现,就要找到对应的实现类才可以。
在Antiforgery
这个项目中,有这样一个扩展方法AntiforgeryServiceCollectionExtensions
,里面告诉了我们相对应的实现是DefaultAntiforgery
这个类。其实Nancy
的源码看多了,看一下类的命名就应该能知道个八九不离十。
services.TryAddSingleton IAntiforgery, DefaultAntiforgery();
其中还涉及到了IServiceCollection
,但这不是本文的重点,所以不会展开讲这个,只是提出它在 .net core中是一个重要的点。
好了,回归正题!要验证是否是合法的请求,自然要先拿到要验证的内容。
var tokens = await _tokenStore.GetRequestTokensAsync(httpContext);
它是从Cookie
中拿到一个指定的前缀为.AspNetCore.Antiforgery.
的Cookie
,并根据这个Cookie
进行后面相应的判断。下面是验证的具体实现:
public bool TryValidateTokenSet(HttpContext httpContext,AntiforgeryToken cookieToken,AntiforgeryToken requestToken,out string message)
{//去掉了部分非空的判断// Do the tokens have the correct format?if (!cookieToken.IsCookieToken || requestToken.IsCookieToken){message = Resources.AntiforgeryToken_TokensSwapped;return false;}// Are the security tokens embedded in each incoming token identical?if (!object.Equals(cookieToken.SecurityToken, requestToken.SecurityToken)){message = Resources.AntiforgeryToken_SecurityTokenMismatch;return false;}// Is the incoming token meant for the current user?var currentUsername = string.Empty;BinaryBlob currentClaimUid = null;var authenticatedIdentity = GetAuthenticatedIdentity(httpContext.User);if (authenticatedIdentity != null){currentClaimUid = GetClaimUidBlob(_claimUidExtractor.ExtractClaimUid(httpContext.User));if (currentClaimUid == null){currentUsername = authenticatedIdentity.Name ?? string.Empty;}}// OpenID and other similar authentication schemes use URIs for the username.// These should be treated as case-sensitive.var comparer = StringComparer.OrdinalIgnoreCase;if (currentUsername.StartsWith("http://", StringComparison.OrdinalIgnoreCase) ||currentUsername.StartsWith("https://", StringComparison.OrdinalIgnoreCase)){comparer = StringComparer.Ordinal;}if (!comparer.Equals(requestToken.Username, currentUsername)){message = Resources.FormatAntiforgeryToken_UsernameMismatch(requestToken.Username, currentUsername);return false;}if (!object.Equals(requestToken.ClaimUid, currentClaimUid)){message = Resources.AntiforgeryToken_ClaimUidMismatch;return false;}// Is the AdditionalData valid?if (_additionalDataProvider != null &&!_additionalDataProvider.ValidateAdditionalData(httpContext, requestToken.AdditionalData)){message = Resources.AntiforgeryToken_AdditionalDataCheckFailed;return false;}message = null;return true;
}
注:验证前还有一个反序列化的过程,这个反序列化就是从
Cookie
中拿到要判断的cookietoken
和requesttoken
如何使用
前面粗略介绍了一下其内部的实现,下面再用个简单的例子来看看具体的使用情况:
使用一:常规的Form
表单
先在视图添加一个Form
表单
<form id="form1" action="/home/antiform" method="post">@Html.AntiForgeryToken()<p><input type="text" name="message" /></p><p><input type="submit" value="Send by Form" /></p>
</form>
在控制器添加一个Action
[ValidateAntiForgeryToken]
[HttpPost]
public IActionResult AntiForm(string message)
{return Content(message);
}
来看看生成的html
是不是如我们前面所说,将@Html.AntiForgeryToken()
输出为一个name
为__RequestVerificationToken
的隐藏域:
再来看看cookie
的相关信息:
可以看到,一切都还是按照前面所说的执行。在输入框输入信息并点击按钮也能正常显示我们输入的文字。
使用二:Ajax
提交
表单:
<form id="form2" action="/home/antiajax" method="post">@Html.AntiForgeryToken()<p><input type="text" name="message" id="ajaxMsg" /></p><p><input type="button" id="btnAjax" value="Send by Ajax" /></p>
</form>
js:
$(function () {$("#btnAjax").on("click", function () {$("#form2").submit(); });
})
这样子的写法也是和上面的结果是一样的!
怕的是出现下面这样的写法:
$.ajax({type: "post",dataType: "html",url: '@Url.Action("AntiAjax", "Home")',data: { message: $('#ajaxMsg').val() },success: function (result) {alert(result);},error: function (err, scnd) {alert(err.statusText);}
});
这样,正常情况下确实是看不出任何毛病,但是实际确是下面的结果(400错误):
相信大家也都发现了问题的所在了!!隐藏域的相关内容并没有一起post过去!!
处理方法有两种:
方法一:
在data
中加上隐藏域相关的内容,大致如下:
$.ajax({//data: { message: $('#ajaxMsg').val(), __RequestVerificationToken: $("input[name='__RequestVerificationToken']").val()}
});
方法二:
在请求中添加一个header
$("#btnAjax").on("click", function () {var token = $("input[name='__RequestVerificationToken']").val();$.ajax({type: "post",dataType: "html",url: '@Url.Action("AntiAjax", "Home")',data: { message: $('#ajaxMsg').val() },headers:{"RequestVerificationToken": token},success: function (result) {alert(result);},error: function (err, scnd) {alert(err.statusText);}});
});
这样就能处理上面出现的问题了!
使用三:自定义相关信息
可能会有不少人觉得,像那个生成的隐藏域那个name
能不能换成自己的,那个cookie
的名字能不能换成自己的??
答案是肯定可以的,下面简单示范一下:
在Startup
的ConfigureServices
方法中,添加下面的内容即可对默认的名称进行相应的修改。
services.AddAntiforgery(option =>
{option.CookieName = "CUSTOMER-CSRF-COOKIE";option.FormFieldName = "CustomerFieldName";option.HeaderName = "CUSTOMER-CSRF-HEADER";
});
相应的,ajax
请求也要做修改:
var token = $("input[name='CustomerFieldName']").val();//隐藏域的名称要改
$.ajax({type: "post",dataType: "html",url: '@Url.Action("AntiAjax", "Home")',data: { message: $('#ajaxMsg').val() },headers:{"CUSTOMER-CSRF-HEADER": token //注意header要修改},success: function (result) {alert(result);},error: function (err, scnd) {alert(err.statusText);}
});
下面是效果:
Form
表单:
Cookie:
mvc 当中 [ValidateAntiForgeryToken] 的作用及用法相关推荐
- java package作用_java import、package作用与用法
java import.package作用与用法 有些人写了一阵子Java,可是对於Java 的package 跟import 还是不太了解很多人以為原始码 .java 档案中的import 会让编译 ...
- php中pre标签,html中pre标签与code标签的作用与用法
HTML 标签 定义和用法 pre 元素可定义预格式化的文本.被包围在 pre 元素中的文本通常会保留空格和换行符.而文本也会呈现为等宽字体. 标签的一个常见应用就是用来表示计算机的源代码. 可以导致 ...
- C#中using关键字的作用及其用法(转)
C#中using关键字的作用及其用法 using的用途和使用技巧. using关键字微软MSDN上解释总共有三种用途: 1.引用命名空间. 2.为命名空间或类型创建别名. ...
- PreTranslateMessage作用和用法
PreTranslateMessage作用和用法 PreTranslateMessage是消息在送给TranslateMessage函数之前被调用的,绝大多数本窗体的消息都要通过这里,比較经常使用,当 ...
- MySQL数据类型中DECIMAL的作用和用法
在MySQL数据类型中,例如INT,FLOAT,DOUBLE,CHAR,DECIMAL等,它们都有各自的作用,下面我们就主要来介绍一下MySQL数据类型中的DECIMAL类型的作用和用法. 一般赋予浮 ...
- PHP中的常见魔术方法功能作用及用法实例
这篇文章主要介绍了PHP中的常见魔术方法功能作用及用法实例,本文讲解了构造函数和析构函数__construct()和__desctruct()以及属性重载(Property Overloading)_ ...
- ASP.NET Core Web 应用程序系列(二)- 在ASP.NET Core中使用Autofac替换自带DI进行批量依赖注入(MVC当中应用)...
在上一章中主要和大家分享在MVC当中如何使用ASP.NET Core内置的DI进行批量依赖注入,本章将继续和大家分享在ASP.NET Core中如何使用Autofac替换自带DI进行批量依赖注入. P ...
- 、简述global关键字的作用_在C#编程中global关键字的作用及其用法
在C#编程中,global 是 C# 2.0 中新增的关键字,理论上说,如果代码写得好的话,根本不需要用到它.今天就为大家展示下global关键字的作用及其用法,希望对大家学习C#编程有所帮助. 假设 ...
- chrome中Blackbox Script 黑盒脚本作用及用法
chrome中Blackbox Script 黑盒脚本作用及用法 Blackbox Script功能 每天前端人员在调试代码的时候,都可能会遇到代码调试着会进入第三方库文件,带来很多没必要的调试,不方 ...
最新文章
- 阿里新生的面试经,与老人分享的职业进阶攻略及规划
- java 图片层级_Java Collection 和Map类层次结构图
- Boost:是否支持sse4.1指令的测试程序
- 入职体检——项目列表(11项)
- 应用开发框架之——根据数据表中的存储的方法名称来调用方法
- Python爬虫 senlenium爬取拉勾网招聘数据,你学会了吗
- 最新京东炸年兽活动一键做任务工具v1.4
- [NOIP2015] 运输计划(第二弹)
- RoomIt屏幕画笔工具
- 百度网盘无提取码分享文件方法
- 谷歌应用内购神器Freedom原理解析
- 计算机配置路径,计算机基础~Windows工作路径、相对路径、绝对路径
- T和?是什么 ?有什么区别?
- android跑马灯监听,android跑马灯成效
- 增量式编码器和绝对式编码器区别
- ClientId 解释
- 我是漂移王-说明介绍
- 386高校毕业设计选题
- 支付宝扫码枪流程笔记
- SecureCRSecureFXPortable64 下载
热门文章
- c 语言getpid头文件,c – 需要当前进程的PID,getpid()返回-1
- Windows10安装CUDA9、cuDNN7、Tensorflow1.5
- 论文翻译:基于深度卷积神经网络的肉鸡粪便识别与分类
- 火云开发课堂 - 《Shader从入门到精通》系列 第十六节:在Shader中对3D模型使用纹理
- 如何快速把一个11g数据库插入到12c cdb中去?
- 微信小程序(第二十二章)- 表单数据提交
- 阿里高工内产的 SpringBoot 保姆级笔记,面面俱到,太全了
- phpCMS后台getshell
- 如何利用网络信息处理规范耽美文学并给读者带来更好的阅读体验
- 使用驱动器中的光盘之前需要将其格式化咋解决?