简介
场景
某程序员大神God在某在线银行Online Bank给他的朋友Friend转账。
转账后,出于好奇,大神God查看了网站的源文件,以及捕获到转账的请求。
大神God发现,这个网站没有做防止CSRF的措施,而且他自己也有一个有一定访问量的网站,于是,他计划在自己的网站上内嵌一个隐藏的Iframe伪造请求(每10s发送一次),来等待鱼儿Fish上钩,给自己转账。
网站源码:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312" />
<title></title>
</head>
<body>
<div>
我是一个内容丰富的网站,你不会关闭我!
</div> <iframe name="frame" src="invalid.html" sandbox="allow-same-origin allow-scripts allow-forms" style="display: none; width: 800px; height: 1000px;"> </iframe>
<script type="text/javascript">
setTimeout("self.location.reload();", 10000);
</script>
</body>
</html>
伪造请求源码:
<html>
<head>
<title></title>
</head>
<body>
<form id="theForm" action="http://localhost:22699/Home/Transfer" method="post">
<input class="form-control" id="TargetUser" name="TargetUser" placeholder="用户名" type="text" value="God" />
<input class="form-control" id="Amount" name="Amount" placeholder="转账金额" type="text" value="100" />
</form> <script type="text/javascript">
document.getElementById('theForm').submit();
</script>
</body>
</html>
鱼儿Fish打开了大神God的网站,在上面浏览丰富多彩的内容。此时伪造请求的结果是这样的(为了演示效果,去掉了隐藏):
因为鱼儿Fish没有登陆,所以,伪造请求一直无法执行,一直跳转回登录页面。
然后鱼儿Fish想起了要登录在线银行Online Bank查询内容,于是他登录了Online Bank。
此时伪造请求的结果是这样的(为了演示效果,去掉了隐藏):
鱼儿Fish每10秒会给大神God转账100元。
防止CSRF
CSRF能成功是因为同一个浏览器会共享Cookies,也就是说,通过权限认证和验证是无法防止CSRF的。那么应该怎样防止CSRF呢?其实防止CSRF的方法很简单,只要确保请求是自己的站点发出的就可以了。那怎么确保请求是发自于自己的站点呢?ASP.NET以Token的形式来判断请求。
我们需要在我们的页面生成一个Token,发请求的时候把Token带上。处理请求的时候需要验证Cookies+Token。
此时伪造请求的结果是这样的(为了演示效果,去掉了隐藏):
$.ajax
如果我的请求不是通过Form提交,而是通过Ajax来提交,会怎样呢?结果是验证不通过。
为什么会这样子?我们回头看看加了@Html.AntiForgeryToken()后页面和请求的变化。
1. 页面多了一个隐藏域,name为__RequestVerificationToken。
2. 请求中也多了一个字段__RequestVerificationToken。
原来要加这么个字段,我也加一个不就可以了!
啊!为什么还是不行...逼我放大招,研究源码去!
噢!原来token要从Form里面取。但是ajax中,Form里面并没有东西。那token怎么办呢?我把token放到碗里,不对,是放到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")';
}
});
});
});
在服务端,参考ValidateAntiForgeryTokenAttribute,编写一个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);
}
}
然后调用时把ValidateAntiForgeryToken替换成AjaxValidateAntiForgeryToken。
大功告成,好有成就感!
全局处理
如果所有的操作请求都要加一个ValidateAntiForgeryToken或者AjaxValidateAntiForgeryToken,不是挺麻烦吗?可以在某个地方统一处理吗?答案是阔仪的。
ValidateAntiForgeryTokenAttribute继承IAuthorizationFilter,那就在AuthorizeAttribute里做统一处理吧。
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注册一下。
FAQ:
1. BypassCsrfValidationAttribute是什么鬼?不是有个AllowAnonymousAttribute吗?
如果有些操作你不需要做CSRF的处理,比如附件上传,你可以在对应的Controller或Action上添加BypassCsrfValidationAttribute。
AllowAnonymousAttribute不仅会绕过CSRF的处理,还会绕过认证和验证。BypassCsrfValidationAttribute绕过CSRF但不绕过认证和验证,
也就是BypassCsrfValidationAttribute作用于那些登录或授权后的Action。
2. 为什么只处理POST请求?
我开发的时候有一个原则,查询都用GET,操作用POST,而对于查询的请求没有必要做CSRF的处理。大家可以按自己的需要去安排!
3. 我做了全局处理,然后还在Controller或Action上加了ValidateAntiForgeryToken或者AjaxValidateAntiForgeryToken,会冲突吗?
不会冲突,只是验证会做两次。
源码下载
为了方便使用,我没有使用任何数据库,而是用了一个文件来存储数据。代码下载后可以直接运行,无需配置。
Web安全相关(二):跨站请求伪造(CSRF/XSRF)的更多相关文章
-
跨站请求伪造 CSRF / XSRF<;一:介绍>;
跨站请求伪造(英语:Cross-site request forgery),也被称为 one-click attack 或者 session riding,通常缩写为 CSRF 或者 XSRF, 是一 ...
-
跨站请求伪造 CSRF / XSRF<;二:应用>;
防御的方法主要有两种<java示例> 1.检查Referer字段 HTTP头中有一个Referer字段,这个字段用以标明请求来源于哪个地址.在处理敏感数据请求时,通常来说,Referer字 ...
-
django之跨站请求伪造csrf
目录 跨站请求伪造 csrf 钓鱼网站 模拟实现 针对form表单 ajax请求 csrf相关的两个装饰器 跨站请求伪造 csrf 钓鱼网站 就类似于你搭建了一个跟银行一模一样的web页面 , 用户在 ...
-
跨站请求伪造(CSRF)-简述
跨站请求伪造(CSRF)-简述 跨站请求伪造(英语:Cross-site request forgery),也被称为 one-click attack 或者 session riding,通常缩写为 ...
-
跨站请求伪造(CSRF)攻击原理解析:比你所想的更危险
跨站请求伪造(CSRF)攻击原理解析:比你所想的更危险 跨站请求伪造(Cross-Site Request Forgery)或许是最令人难以理解的一种攻击方式了,但也正因如此,它的危险性也被人们所低估 ...
-
PHP安全编程:跨站请求伪造CSRF的防御(转)
跨站请求伪造(CSRF)是一种允许攻击者通过受害者发送任意HTTP请求的一类攻击方法.此处所指的受害者是一个不知情的同谋,所有的伪造请求都由他发起,而不是攻击者.这样,很你就很难确定哪些请求是属于跨站 ...
-
.NET Core实战项目之CMS 第十四章 开发篇-防止跨站请求伪造(XSRF/CSRF)攻击处理
通过 ASP.NET Core,开发者可轻松配置和管理其应用的安全性. ASP.NET Core 中包含管理身份验证.授权.数据保护.SSL 强制.应用机密.请求防伪保护及 CORS 管理等等安全方面 ...
-
Web安全测试之跨站请求伪造(CSRF)篇
跨站请求伪造(即CSRF)被Web安全界称为诸多漏洞中“沉睡的巨人”,其威胁程度由此“美誉”便可见一斑.本文将简单介绍该漏洞,并详细说明造成这种漏洞的原因所在,以及针对该漏洞的黑盒测试与灰盒子测试具体 ...
-
ASP.NET Core 防止跨站请求伪造(XSRF/CSRF)攻击 (转载)
什么是反伪造攻击? 跨站点请求伪造(也称为XSRF或CSRF,发音为see-surf)是对Web托管应用程序的攻击,因为恶意网站可能会影响客户端浏览器和浏览器信任网站之间的交互.这种攻击是完全有可能的 ...
-
跨站请求伪造(csrf)中间件整理
一. CSRF中间件 字面意思跨站请求伪造; 即模仿个请求朝服务器发送,django中对跨站伪造的请求有相应的校验 from django.views.decorators.csrf import c ...
随机推荐
-
C# Azure 存储-队列
1.前言 本篇文章是根据Azure的官网document总结,如果想直接跳过本文章,可以点击下面的链接进入. https://www.azure.cn/zh-cn/documentation/arti ...
-
Django实现一个相片管理系统01
有些日子没写笔记,O(∩_∩)O哈哈~实在是肚子没有墨水啦!今天不写数据结构啦!多怀念研究数据结构的日子啊! 可是呢!最近有个项目要搞图像管理方面的,具体内容就不说啦!我们今天来实现一个简单的相册管理 ...
-
C#线程系列讲座(2):Thread类的应用
一.Thread类的基本用法 通过System.Threading.Thread类可以开始新的线程,并在线程堆栈中运行静态或实例方法.可以通过Thread类的的构造方法传递一个无参数,并且不返回值(返 ...
-
OpenGL ES应用开发实践指南:iOS卷
<OpenGL ES应用开发实践指南:iOS卷> 基本信息 原书名:Learning OpenGL ES for iOS:A Hands-On Guide to Modern 3D Gra ...
-
使用Integer类实现二叉树排序
class BinaryTree { class Node { private Comparable data; private Node left; ...
-
java中用正則表達式推断中文字符串中是否含有英文或者数字
public static boolean includingNUM(String str)throws Exception{ Pattern p = Pattern.compile(" ...
-
hdu_2899_Strange fuction(三分查找)
题目连接:http://acm.hdu.edu.cn/showproblem.php?pid=2899 题意:让你解方程 题解:对于只有一个凸或者没有凸的图像,可以直接上三分解决. #include& ...
-
笔记:Struts2 拦截器
配置拦截器 Struts.xml 配置文件中,使用<interceptor-/>来定义拦截器,有属性 name 表示拦截器的名称,class 表示拦截器的具体首先类,可以使用<par ...
-
POJ 1966 Cable TV Network (点连通度)【最小割】
<题目链接> 题目大意: 给定一个无向图,求点连通度,即最少去掉多少个点使得图不连通. 解题分析: 解决点连通度和边连通度的一类方法总结见 >>> 本题是求点连通度, ...
-
oracle问题 ORA-01843:not a valid month
解决思路: 开始解决问题走了些弯路,搜了一些资料,结果大部分说的是修改会话的nls_date_language参数 可是线上正式项目,不能说改就改吧 就找其他方式解决 最终找到问题,to_date() ...