我要学ASP.NET MVC 3.0(十三): MVC 3.0 防止跨站点请求伪造 (CSRF) 攻击

概述

    

众所周知,ASP.Net MVC程序在浏览器运行时产生了标准的Html标签,包括浏览器要发送的关键数据等内容都在Html内容里面,听起来不错,但是假如我们仿造类似的Html内容,更改里面关键数据,在浏览器运行起来会怎么样呢?好下面我们就做这样一个例子。

     

CSRF攻击例子

      

首先我们拿以前做好的person/edit作为例子

先看控制器代码

     //初始页面
       
// GET: /Person/Edit/5

       
public ActionResult Edit(int id)
       {
           
return View();
       }

       
//修改方法
       
// POST: /Person/Edit/5
       [HttpPost]
       
public ActionResult Edit(int id, Person person)
       {
           
try
           {
               
// 数据库操作代码

               
return RedirectToAction("Success",person);
           }
           
catch
           {
               
return View();
           }
       }

  

然后我们来看看视图代码

@model MvcApplication.Models.Person
@{
   ViewBag.Title = "修改人员";
   Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>
   修改人员
</h2>
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
@using (Html.BeginForm())
{
   @Html.ValidationSummary(true)
   
<fieldset>
       
<legend>人员信息</legend>
       @Html.HiddenFor(model => model.ID)
       
<div class="editor-label">
           @Html.LabelFor(model => model.Name)
       
</div>
       
<div class="editor-field">
           @Html.EditorFor(model => model.Name)
           @Html.ValidationMessageFor(model => model.Name)
       
</div>
       
<div class="editor-label">
           @Html.LabelFor(model => model.Age)
       
</div>
       
<div class="editor-field">
           @Html.EditorFor(model => model.Age)
           @Html.ValidationMessageFor(model => model.Age)
       
</div>
       
<p>
           
<input type="submit" value="保存" />
       
</p>
   
</fieldset>
}
<div>
   @Html.ActionLink("返回列表", "Index")
</div>

  

运行起来看看

点击保存后

可以看到,上面例子中的代码都是正确的,运行都是正常的,下面我们来实现CSRF攻击。

       

实现CSRF攻击

       

打开记事本,写入下面代码

 <body onload="document.getElementById('fm1').submit()"> 
   
<form id="fm1" action="http://localhost:5132/person/Edit/1001" method="post">
       
<input name="name" value="张三" />
       
<input name="age" value="21" />
   
</form>
</body>

  

另存为Html文件。

双击文件运行。。。

允许阻止的内容之后:

啊???~~~不会吧,此时你可能已经傻了吧。。。这个就叫CSRF攻击。

      

什么是CSRF攻击

       

CSRF(Cross-site request forgery),中文名称:跨站请求伪造,也被称为:one click attack/session riding,缩写为:CSRF/XSRF。

因为在ASP.NET程序中,我们的用户信息都是存在与cookies里面的,此时在用户自己来说,程序已经可以算是裸奔了。正因为如此,Web程序接受的正常客户端请求一般来自用户的点击链接和表单提交等行为。可是恶意攻击者却可以依靠脚本和浏览器的安全缺陷来劫持客户端会话、伪造客户端请求。攻击者盗用了你的身份,以你的名义发送恶意请求,以你名义发送邮件,发消息,盗取你的账号,甚至于购买商品,虚拟货币转账......造成的问题包括:个人隐私泄露以及财产安全。这就是CSRF攻击。

CSRF漏洞的攻击一般分为站内和站外两种类型:

CSRF站内类型的漏洞在一定程度上是由于程序员滥用$_REQUEST类变量造成的,一些敏感的操作本来是要求用户从表单提交发起POST请求传参给程序,但是由于使用了$_REQUEST等变量,程序也接收GET请求传参,这样就给攻击者使用CSRF攻击创造了条件,一般攻击者只要把预测好的请求参数放在站内一个贴子或者留言的图片链接里,受害者浏览了这样的页面就会被强迫发起请求。

CSRF站外类型的漏洞其实就是传统意义上的外部提交数据问题,一般程序员会考虑给一些留言评论等的表单加上水印以防止SPAM问题,但是为了用户的体验性,一些操作可能没有做任何限制,所以攻击者可以先预测好请求的参数,在站外的Web页面里编写javascript脚本伪造文件请求或和自动提交的表单来实现GET、POST请求,用户在会话状态下点击链接访问站外的Web页面,客户端就被强迫发起请求。

浏览器的安全缺陷

现在的Web应用程序几乎都是使用Cookie来识别用户身份以及保存会话状态,但是所有的浏览器在最初加入Cookie功能时并没有考虑安全因素,从WEB页面产生的文件请求都会带上COOKIE

       

MVC中防止CSRF攻击

        

使用AntiForgeryToken令牌,在ASP.NET的核心中为我们提供了一个用来检测和组织CSRF攻击的令牌。

只要在Html表单里面使用了@Html.AntiForgeryToken()就可以阻止CSRF攻击。

@model MvcApplication.Models.Person
@{
   ViewBag.Title = "修改人员";
   Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>
   修改人员
</h2>
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
@using (Html.BeginForm())
{
   @Html.AntiForgeryToken()
   @Html.ValidationSummary(true)
   
<fieldset>
       
<legend>人员信息</legend>
       @Html.HiddenFor(model => model.ID)
       
<div class="editor-label">
           @Html.LabelFor(model => model.Name)
       
</div>
       
<div class="editor-field">
           @Html.EditorFor(model => model.Name)
           @Html.ValidationMessageFor(model => model.Name)
       
</div>
       
<div class="editor-label">
           @Html.LabelFor(model => model.Age)
       
</div>
       
<div class="editor-field">
           @Html.EditorFor(model => model.Age)
           @Html.ValidationMessageFor(model => model.Age)
       
</div>
       
<p>
           
<input type="submit" value="保存" />
       
</p>
   
</fieldset>
}
<div>
   @Html.ActionLink("返回列表", "Index")
</div>

  

相应的我们要在Controller中也要加入[ValidateAntiForgeryToken]过滤特性。

该特性表示检测服务器请求是否被篡改。

注意:该特性只能用于post请求,get请求无效。

        //修改方法
       
// POST: /Person/Edit/5
       [ValidateAntiForgeryToken]
       [HttpPost]
       
public ActionResult Edit(int id, Person person)
       {
           
try
           {
               
// 数据库操作代码

               
return RedirectToAction("Success",person);
           }
           
catch
           {
               
return View();
           }
       }

  

 运行效果和上面的一样

然后我们在运行刚才保存的Html文件看看

 哈哈报错了。。那就证明我们现在的阻止CSRF攻击是有效的。

       

使用Salt值加强保护

      

为了保证我们的AntiForgeryToken阻止在程序中唯一,更好的加密AntiForgeryToken,我们可以为AntiForgeryToken设置Salt值。

这样,即使攻击者设法得到了令牌,但是如果Salt值不匹配也不能进行post操作。

        //修改方法
       
// POST: /Person/Edit/5
       [ValidateAntiForgeryToken(Salt = "aa")]
       [HttpPost]
       
public ActionResult Edit(int id, Person person)
       {
           
try
           {
               
// 数据库操作代码

               
return RedirectToAction("Success",person);
           }
           
catch
           {
               
return View();
           }
       }

  

我们暂时不修改View代码

运行看看

可以看到加入Salt值后即使是MVC程序本身的页面都无法请求,更别说攻击者了,除非他能猜到我们的Salt值。

我们修改view代码

    @Html.AntiForgeryToken("aa")

  

继续运行项目

可以看到在view中加入Salt值后,阻止就变的有目的性了。

      

总结

                                                                                                    

浏览器的会话安全特性 :

我们参照Set-Cookie的标准格式

Set-Cookie: <name>=<value>[; <name>=<value>] [; expires=<date>][; domain=<domain_name>] [; path=<some_path>][; secure][; HttpOnly]

 

浏览器支持的cookie实际上分为两种形式:

一种是内存COOKIE,在没有设定COOKIE值的expires参数,也就是没有设置COOKIE的失效时间情况下,这个COOKIE在关闭浏览器后将失效,并且不会保存在本地。另外一种是本地保存COOKIE,也就是设置了expires参数,COOKIE的值指定了失效时间,那么这个COOKIE会保存在本地,关闭浏览器后再访问网站,在COOKIE有效时间内所有的请求都会带上这个本地保存COOKIE。

Internet Explorer有一个隐私报告功能,其实这是一个安全功能,它会阻挡所有的第三方COOKIE,比如A域Web页面嵌入了B域的文件,客户端浏览器访问了A域的Web页面后对B域所发起的文件请求所带上的COOKIE会被IE拦截。除开文件请求情况,A域的Web页面如果使用IFRAME帧包含B域的Web页面,访问A域的Web页面后,B域的Web页面里的所有请求包括文件请求带上的COOKIE同样会被IE拦截。不过Internet Explorer的这个安全功能有两个特性,一是不会拦截内存COOKIE,二是在网站设置了P3P头的情况下,会允许跨域访问COOKIE,隐私报告功能就不会起作用了。

所以在Internet Explorer的这个安全特性的前提下,攻击者要进行站外的CSRF攻击使用文件请求来伪造GET请求的话,受害者必须在使用内存COOKIE也就是没有保存登陆的会话状态下才可能成功。而Firefox浏览器并没有考虑使用这样的功能,站外的CSRF攻击完全没有限制。

 

关于Javascript劫持技术 :

近年来的web程序频繁使用Ajax技术,JSON也开始取代XML做为AJAX的数据传输格式,JSON实际上就是一段javascript,大部分都是定义的数组格式。fortify公司的三位安全人员在2007年提出了Javascript劫持技术,这是一种针对JSON动态数据的攻击方式,实际上这也是一种变相的CSRF攻击。攻击者从站外调用一个script标签包含站内的一个JSON动态数据接口,因为<script src=”>这种脚本标签的文件请求会带上COOKIE,用户访问后相当于被迫从站外发起了一个带有身份认证COOKIE的GET请求,web程序马上返回了用户相关的JSON数据,攻击者就可以取得这些关键的JSON数据加以利用,整个过程相当于一个站外类型的CSRF攻击。

WEB应用中的JSON数据大部分使用在个人资料、好友列表等隐私功能里,这类数据一般是web蠕虫最重要的传播功能所需要的数据,而CSRF攻击结合Javascript劫持技术完全可以分析这类数据制作自动传播的web蠕虫,在一定情况下这种web蠕虫比网站出现跨站脚本漏洞制作的web蠕虫更具威胁性,几乎不受网站架构的限制,因为攻击者利用的不是传统的Web漏洞而是网站自身正常的功能,如果出现这类CSRF蠕虫,对网站的打击将是灾难性的。

 

安全提醒 :

各个大型社区类网站必须警惕CSRF攻击和相关web蠕虫的爆发,并且针对这类web攻击制定有效的应急措施。同建议程序员不要滥用$_REQUEST类变量,在必要的情况下给某些敏感的操作加上水印,考虑使用类似DISCUZ论坛的formhash技术提高黑客预测请求参数的难度,注意JSON数据接口的安全问题等。最后希望大家全面的考虑客户端和服务端整体的安全,注意Internet Explorer等客户端浏览器一些安全缺陷和安全特性,防止客户端程序的安全问题影响整个Web应用程序。

本文转自程序猿博客51CTO博客,原文链接http://blog.51cto.com/haihuiwei/1966358如需转载请自行联系原作者


365850153