ASP.NET MVC防范CSRF最佳实践
XSS与CSRF
哈哈,有点标题党,但我保证这篇文章跟别的不太一样。
我认为,网站安全的基础有三块:
- 防范中间人攻击
- 防范XSS
- 防范CSRF
注意,我讲的是基础,如果更高级点的话可以考虑防范机器人刷单,再高级点就防范DDoS攻击,不过我们还是回到“基础”这个话题上吧,对于中间人攻击,使用HTTPS是正确且唯一的做法,其它都是歪门邪道,最好还要购买各个浏览器都承认的SSL证书;防范XSS,关键点在于将用户提交数据呈现在页面上的时候,需要使用Html Encode,或在处理带HTML格式的用户表单数据时,进行“消毒”(Sanitize)处理,关于这个,我前一篇文章《让ASP.NET接受有“潜在危险”的提交》已经讲述了应该怎么做;剩下了这个CSRF是本文要讲的,我认为防范CSRF的前提是必须先做好XSS的防范工作,因为:CSRF防的是别的网站,如果自己的网站本身有XSS漏洞,被别人注入了有害脚本,那么就变成了“家贼难防”了。
至于什么是CSRF,如何让ASP.NET防范CSRF,这种文章很多的,比如博客园里这篇就可以:《ASP.NET MVC 防止 CSRF 的方法》,我总结一下一般的做法,也就这两点:
- 在cshtml页面的form标签里加上@Html.AntiForgeryToken()
- 在Controller需要防范CSRF的Action上加上[ValidateAntiForgeryToken]注解
Done!
原理
为啥这样就行了呢?我简单说说原理:@Html.AntiForgeryToken()的作用是在页面上插入一个type为hidden的input标签,它的name固定是__RequestVerificationToken,value则是一长串密文,这是真的密文,是经过加密的,那明文是什么?是随机生成的128位数字,跟GUID差不多的东西,我们叫它“随机明文”吧,再附带一点额外的信息(忽略这个吧),然后加密,加密器是这个玩意儿:System.Web.Security.MachineKey,我这里写个简单的Example,大家可以弄个HelloWorld程序看看运行效果。
byte[] plainText = Encoding.UTF8.GetBytes("");
string[] purposes = { "blah blah blah" };
byte[] cypherText = MachineKey.Protect(plainText, purposes);
Console.WriteLine(Convert.ToBase64String(cypherText));
plainText = MachineKey.Unprotect(cypherText, purposes);
Console.WriteLine(Encoding.UTF8.GetString(plainText));
多运行几次,每次都能解出正确的明文,但是密文每次都不一样,所以大家在不断刷新页面的时候,发现每次生成的value也不一样,但没事,它们的“随机明文”是一样的。除了生成这个input标签之外,@Html.AntiForgeryToken()还做了个额外的动作,那就是生成一个同样名字(也叫__RequestVerificationToken)的Cookie,内容差不多,在我们看来也是一长串密文。所以总结回来@Html.AntiForgeryToken()就做了这两件事:
- 页面上加上一个像这样的标签<input name="__RequestVerificationToken" type="hidden" value="(一长串密文)" />
- 生成一个名为__RequestVerificationToken的Cookie,值为“一长串密文”
接下来轮到ValidateAntiForgeryToken过滤器,它收到了请求,就尝试解出请求中的Cookie的“一长串密文”和请求中的Form的“一长串密文”,解密后比对两者的“随机明文”,如果一致,则通过,(其实作为高级用法你还可以自定义一些额外的规则,不过这不在本文讲述范围内)否则抛出HttpAntiForgeryException异常。
为什么只需要检验下Cookie和Form的“随机明文”就可以防范CSRF了呢?其实理解起来并不难,前面说了CSRF防的是别的网站,别的网站伪造了请求,利用访问者的浏览器对目标网站发送了这个请求,但伪造者并不清楚目标网站的访问者的__RequestVerificationToken这个Cookie的值,因此表单中的__RequestVerificationToken的值也就无法伪造,这个请求会被ValidateAntiForgeryToken过滤器拦截下来。
特点与局限性
知道了原理,就来分析下它的特点与局限性,首先很容易想到的就是:
- 浏览器必须要支持Cookie
- 只能验证POST请求(因为需要Form)
另外思考一下如果一个页面中调用了多次@Html.AntiForgeryToken(),生成了多个input标签,会怎样呢?会不会生成了两个不同的Token,最后比对出错?其实不必担心,正儿八经的那个随机明文只会生成一次,Html.AntiForgeryToken()方法会检查你提交的Cookie,如果已存在__RequestVerificationToken,那么它就不会再生成一个新的随机数明文了。否则如果每次都生成一个随机数明文,你的页面上如果有两个Form的话,其中一个肯定没法正常提交,更不用说AJAX提交的情况。
AJAX POST
如果不是直接提交页面上的表单,而是AJAX POST,像这样:
$.ajax({
type: "post",
url: "/testurl",
data: {test:'abc'},
success: function (data) {
//done!
}
});
这可咋办?你必须想方设法在data中带上正确的__RequestVerificationToken啊!StackOverflow上有个解决方案,挺不错,大家参考下:Go to StackOverflow,简单地说就是写一个ASP.NET MVC的HTML生成帮助方法,用于生成那“一长串密文”,交给这个ajax的data。
但这可不是我想说的“最佳实践” ,再考虑一种情况:用js动态生成Form,然后Submit。嗯,我承认这个有点奇葩,但在我的项目中确实有不少地方是这么干的,你别问为什么了,反正就是有,遇到这种情况,咋办?看来还是得求助于js。下面我分享下我的做法:
我的“最佳实践”
首先我没有把@Html.AntiForgeryToken()放到每一个Form中,我只在一处地方用到了@Html.AntiForgeryToken(),那就是母版页!接下来把下面这段js放到common.js中(common.js是母版页引用的js,也就是说每个页面会引用到):
//处理form的submit事件,添加AntiForgeryToken到表单里
$("body").on("submit", "form", function () {
var theForm = $(this);
if (theForm.find("input[name='__RequestVerificationToken']").length === 0) {
var antiForgery = $("input[name='__RequestVerificationToken']:first").val();
if (antiForgery) {
var theAntiForgeryTokenInput = $('<input />').attr('type', 'hidden')
.attr('name', '__RequestVerificationToken')
.attr('value', antiForgery);
$(this).prepend(theAntiForgeryTokenInput);
}
}
});
这样一来,所有的form的submit动作就会在这里被处理一下,添加上了__RequestVerificationToken这个字段。接下来是AJAX POST的处理:
data= {test:'abc'};
var antiForgery = $("input[name='__RequestVerificationToken']:first").val();
if (antiForgery) {
if (!data.__RequestVerificationToken) {
data.__RequestVerificationToken = antiForgery;
}
}
$.ajax({
type: "post",
url: "/testurl",
data: data,
success: function (data) {
//done!
}
});
嗯?你也许要问,每个用到AJAX POST的地方都加上这么一段代码岂不是很繁琐?是的,但在我的项目中,我用了几个公共的方法对AJAX POST进行了一些封装,所以只需要改好这几个地方即可,你可以根据自己的项目的实际情况进行优化处理。
还有一种情况是用AJAX来提交Form,而不是像上面这样的data:
var form = $("#the-form-id");
var dataToSubmit = form.serializeArray();
var antiForgery = $("input[name='__RequestVerificationToken']:first").val();
if (antiForgery) {
var found = false;
for (var i = 0; i < dataToSubmit.length; i++) {
if (dataToSubmit[i].name === '__RequestVerificationToken') {
found = true;
break;
}
}
if (!found) {
dataToSubmit.push({ name: "__RequestVerificationToken", value: antiForgery });
}
}
$.ajax({
type: method,
url: urlToSubmit,
data: dataToSubmit,
success: function (data) {
//done!
}
});
照旧,根据你的项目的实际情况封装一下,其实真正要改动的地方不多,只要你框架搭好了。
总结一下,框架搭好了的前提下,为了防范CSRF,你所需要做的事情就仅剩下:给带[HttpPost]注解的Action添加[ValidateAntiForgeryToken]。
至于验证失败抛出HttpAntiForgeryException异常导致默认错误页面(我又叫它“死黄页”,该死的黄页的意思)出现的问题,你可以在Application_Error中处理一下啊,Google关键字“Application_Error”,一搜一大堆,或者,等我有空了再写一篇这个主题的“最佳实践”?
ASP.NET MVC防范CSRF最佳实践的更多相关文章
- atitit.spring3 mvc url配置最佳实践
atitit.spring3 mvc url配置最佳实践 1. Url-pattern bp 1 2. 通用星号url pattern的问题 1 3. Other code 1 4. 参考 2 1. ...
- ASP.NET Core Web API 最佳实践指南
原文地址: ASP.NET-Core-Web-API-Best-Practices-Guide 介绍 当我们编写一个项目的时候,我们的主要目标是使它能如期运行,并尽可能地满足所有用户需求. 但是,你难 ...
- ASP.NET Core 依赖注入最佳实践与技巧
ASP.NET Core 依赖注入最佳实践与技巧 原文地址:https://medium.com/volosoft/asp-net-core-dependency-injection-best-pra ...
- asp.netcore mvc 防CSRF攻击,原理介绍+代码演示+详细讲解
一.CSRF介绍 1.CSRF是什么? CSRF(Cross-site request forgery),中文名称:跨站请求伪造,也被称为:one click attack/session ridin ...
- ASP.NET Core 性能优化最佳实践
本文提供了 ASP.NET Core 的性能最佳实践指南. 译文原文地址:https://docs.microsoft.com/en-us/aspnet/core/performance/perfor ...
- ASP.NET MVC 防止CSRF攻击
简介 MVC中的Html.AntiForgeryToken()是用来防止跨站请求伪造(CSRF:Cross-site request forgery)攻击的一个措施,它跟XSS(XSS又叫CSS:Cr ...
- [转]ASP.NET Core Web API 最佳实践指南
原文地址: ASP.NET-Core-Web-API-Best-Practices-Guide 转自 介绍# 当我们编写一个项目的时候,我们的主要目标是使它能如期运行,并尽可能地满足所有用户需求. 但 ...
- ASP.NET Core文件压缩最佳实践
前言 在微软官方文档中,未明确指出文件压缩功能的使用误区. 本文将对 ASP.NET Core 文件响应压缩的常见使用误区做出说明. 误区1:未使用 Brotil 压缩 几乎不需要任何额外的代价,Br ...
- ASP.NET MVC应用require.js实践
这里有更好的阅读体验和及时的更新:http://pchou.info/javascript/asp.net/2013/11/10/527f6ec41d6ad.html Require.js是一个支持j ...
随机推荐
- servlet文件下载
创建web工程servlet,新建DownloadServlet.java package com.xmyself.servlet; import java.io.File; import java. ...
- 深入理解JS 执行细节
javascript从定义到执行,JS引擎在实现层做了很多初始化工作,因此在学习JS引擎工作机制之前,我们需要引入几个相关的概念:执行环境栈.全局对象.执行环境.变量对象.活动对象.作用域和作用域链等 ...
- 【调侃】IOC前世今生
前些天,参与了公司内部小组的一次技术交流,主要是针对<IOC与AOP>,本着学而时习之的态度及积极分享的精神,我就结合一个小故事来初浅地剖析一下我眼中的“IOC前世今生”,以方便初学者能更 ...
- C语言 · 整数平均值
编写函数,求包含n个元素的整数数组中元素的平均值.要求在函数内部使用指针操纵数组元素,其中n个整数从键盘输入,输出为其平均值. 样例输入: (输入格式说明:5为输入数据的个数,3 4 0 0 2 是以 ...
- 【WCF】使用“用户名/密码”验证的合理方法
我不敢说俺的方法是最佳方案,反正这世界上很多东西都是变动的,正像老子所说的——“反(返)者,道之动”.以往看到有些文章中说,为每个客户端安装证书嫌麻烦,就直接采用把用户名和密码塞在SOAP头中发送,然 ...
- 23种设计模式--责任链模式-Chain of Responsibility Pattern
一.责任链模式的介绍 责任链模式用简单点的话来说,将责任一步一步传下去,这就是责任,想到这个我们可以相当击鼓传花,这个是为了方便记忆,另外就是我们在项目中经常用到的审批流程等这一类的场景时我们就可以考 ...
- OpenGL超级宝典笔记----框架搭建
自从工作后,总是或多或少的会接触到客户端3d图形渲染,正好自己对于3d图形的渲染也很感兴趣,所以最近打算从学习OpenGL的图形API出发,进而了解3d图形的渲染技术.到网上查了一些资料,OpenGL ...
- 前端性能优化的另一种方式——HTTP2.0
最近在读一本书叫<web性能权威指南>谷歌公司高性能团队核心成员的权威之作. 一直听说HTTP2.0,对此也仅仅是耳闻,没有具体研读过,这次正好有两个篇章,分别讲HTTP1.1和HTTP2 ...
- SQL Server2008R2 在windows8上安装,出现“兼容性”和 “执行未经授权的操作”的错误!
本人是windows8.1的操作系统,亲测安装成功 解决方法如下: 1.卸载干净sql Server2008r2,包括注册表内容,删除c盘下的安装路径! 2.关闭防火墙(这步很重要) 3.断开网络连接 ...
- “老坛泡新菜”:SOD MVVM框架,让WinForms焕发新春
火热的MVVM框架 最近几年最热门的技术之一就是前端技术了,各种前端框架,前端标准和前端设计风格层出不穷,而在众多前端框架中具有MVC,MVVM功能的框架成为耀眼新星,比如GitHub关注度很高的Vu ...