澄泉 發表於 2015-12-14 14:54:19

防止CSRF攻击ASP.NET应用

<p>CSRF是什么?</p>
<p>  CSRF(Cross-site request forgery),中文名称:跨站请求伪造,也被称为:one click attack/session riding,缩写为:CSRF/XSRF。CSRF(Cross Site Request Forgery, 跨站域请求伪造)是一种网络的攻击方式,它在 2007 年曾被列为互联网 20 大安全隐患之一。其他安全隐患,比如 SQL 脚本注入,跨站域脚本攻击等在近年来已经逐渐为众人熟知,很多网站也都针对他们进行了防御。然而,对于大多数人来说,CSRF 却依然是一个陌生的概念。即便是大名鼎鼎的 Gmail, 在 2007 年底也存在着 CSRF 漏洞,从而被黑客攻击而使 Gmail 的用户造成巨大的损失。</p>
<p><strong>CSRF可以做什么?</strong></p>
<p>  你这可以这么理解CSRF攻击:攻击者盗用了你的身份,以你的名义发送恶意请求。CSRF能够做的事情包括:以你名义发送邮件,发消息,盗取你的账号,甚至于购买商品,虚拟货币转账......造成的问题包括:个人隐私泄露以及财产安全。</p>
<p><strong>CSRF攻击原理</strong></p>
<p align="center"><img id="theimg" onclick="window.open(this.src)" src="https://img.jbzj.com/file_images/article/201512/20151214150059925.jpg?2015111415112" alt="" /></p>
<p align="left">从上图可以看出,要完成一次CSRF攻击,受害者必须依次完成两个步骤:</p>
<p>  1.登录受信任网站A,并在本地生成Cookie。</p>
<p>  2.在不登出A的情况下,访问危险网站B。</p>
<p>  看到这里,你也许会说:&ldquo;如果我不满足以上两个条件中的一个,我就不会受到CSRF的攻击&rdquo;。是的,确实如此,但你不能保证以下情况不会发生:</p>
<p>  1.你不能保证你登录了一个网站后,不再打开一个tab页面并访问另外的网站。</p>
<p>  2.你不能保证你关闭浏览器了后,你本地的Cookie立刻过期,你上次的会话已经结束。(事实上,关闭浏览器不能结束一个会话,但大多数人都会错误的认为关闭浏览器就等于退出登录/结束会话了......)</p>
<p>  3.上图中所谓的攻击网站,可能是一个存在其他漏洞的可信任的经常被人访问的网站。</p>
<p>上面大概地讲了一下CSRF攻击的思想,下面我将用个例子详细说说具体的CSRF攻击,这里我以一个银行转账的操作作为例子(仅仅是例子,真实的银行网站没这么傻:&gt;)</p>
<p>  银行网站A,它以GET请求来完成银行转账的操作,如:http://www.mybank.com/Transfer.php?toBankId=11&amp;money=1000</p>
<p>  危险网站B,它里面有一段HTML的代码如下:</p>
<p>  <font color="#3366ff">&lt;img src=http://www.mybank.com/Transfer.php?toBankId=11&amp;money=1000&gt;</font></p>
<p>  首先,你登录了银行网站A,然后访问危险网站B,噢,这时你会发现你的银行账户少了1000块......</p>
<p>  为什么会这样呢?原因是银行网站A违反了HTTP规范,使用GET请求更新资源。在访问危险网站B的之前,你已经登录了银行网站A,而B中的&lt;img&gt;以GET的方式请求第三方资源(这里的第三方就是指银行网站了,原本这是一个合法的请求,但这里被不法分子利用了),所以你的浏览器会带上你的银行网站A的Cookie发出Get请求,去获取资源&ldquo;http://www.mybank.com/Transfer.php?toBankId=11&amp;money=1000&rdquo;,结果银行网站服务器收到请求后,认为这是一个更新资源操作(转账操作),所以就立刻进行转账操作......</p>
<p><strong>当前防御 CSRF 的几种策略</strong></p>
<p>在业界目前防御 CSRF 攻击主要有三种策略:验证 HTTP Referer 字段;在请求地址中添加 token 并验证;在 HTTP 头中自定义属性并验证。下面就分别对这三种策略进行详细介绍。</p>
<p><font color="#800000"><strong>验证 HTTP Referer 字段</strong></font></p>
<p>根据 HTTP 协议,在 HTTP 头中有一个字段叫 Referer,它记录了该 HTTP 请求的来源地址。在通常情况下,访问一个安全受限页面的请求来自于同一个网站,比如需要访问 http://bank.example/withdraw?account=bob&amp;amount=1000000&amp;for=Mallory,用户必须先登陆 bank.example,然后通过点击页面上的按钮来触发转账事件。这时,该转帐请求的 Referer 值就会是转账按钮所在的页面的 URL,通常是以 bank.example 域名开头的地址。而如果黑客要对银行网站实施 CSRF 攻击,他只能在他自己的网站构造请求,当用户通过黑客的网站发送请求到银行时,该请求的 Referer 是指向黑客自己的网站。因此,要防御 CSRF 攻击,银行网站只需要对于每一个转账请求验证其 Referer 值,如果是以 bank.example 开头的域名,则说明该请求是来自银行网站自己的请求,是合法的。如果 Referer 是其他网站的话,则有可能是黑客的 CSRF 攻击,拒绝该请求。</p>
<p>这种方法的显而易见的好处就是简单易行,网站的普通开发人员不需要操心 CSRF 的漏洞,只需要在最后给所有安全敏感的请求统一增加一个拦截器来检查 Referer 的值就可以。特别是对于当前现有的系统,不需要改变当前系统的任何已有代码和逻辑,没有风险,非常便捷。</p>
<p>然而,这种方法并非万无一失。Referer 的值是由浏览器提供的,虽然 HTTP 协议上有明确的要求,但是每个浏览器对于 Referer 的具体实现可能有差别,并不能保证浏览器自身没有安全漏洞。使用验证 Referer 值的方法,就是把安全性都依赖于第三方(即浏览器)来保障,从理论上来讲,这样并不安全。事实上,对于某些浏览器,比如 IE6 或 FF2,目前已经有一些方法可以篡改 Referer 值。如果 bank.example 网站支持 IE6 浏览器,黑客完全可以把用户浏览器的 Referer 值设为以 bank.example 域名开头的地址,这样就可以通过验证,从而进行 CSRF 攻击。</p>
<p>即便是使用最新的浏览器,黑客无法篡改 Referer 值,这种方法仍然有问题。因为 Referer 值会记录下用户的访问来源,有些用户认为这样会侵犯到他们自己的隐私权,特别是有些组织担心 Referer 值会把组织内网中的某些信息泄露到外网中。因此,用户自己可以设置浏览器使其在发送请求时不再提供 Referer。当他们正常访问银行网站时,网站会因为请求没有 Referer 值而认为是 CSRF 攻击,拒绝合法用户的访问。</p>
<p><strong><font color="#800000">在请求地址中添加 token 并验证</font></strong></p>
<p>CSRF 攻击之所以能够成功,是因为黑客可以完全伪造用户的请求,该请求中所有的用户验证信息都是存在于 cookie 中,因此黑客可以在不知道这些验证信息的情况下直接利用用户自己的 cookie 来通过安全验证。要抵御 CSRF,关键在于在请求中放入黑客所不能伪造的信息,并且该信息不存在于 cookie 之中。可以在 HTTP 请求中以参数的形式加入一个随机产生的 token,并在服务器端建立一个拦截器来验证这个 token,如果请求中没有 token 或者 token 内容不正确,则认为可能是 CSRF 攻击而拒绝该请求。</p>
<p>这种方法要比检查 Referer 要安全一些,token 可以在用户登陆后产生并放于 session 之中,然后在每次请求时把 token 从 session 中拿出,与请求中的 token 进行比对,但这种方法的难点在于如何把 token 以参数的形式加入请求。对于 GET 请求,token 将附在请求地址之后,这样 URL 就变成 http://url?csrftoken=tokenvalue。 而对于 POST 请求来说,要在 form 的最后加上 &lt;input type=&rdquo;hidden&rdquo; name=&rdquo;csrftoken&rdquo; value=&rdquo;tokenvalue&rdquo;/&gt;,这样就把 token 以参数的形式加入请求了。但是,在一个网站中,可以接受请求的地方非常多,要对于每一个请求都加上 token 是很麻烦的,并且很容易漏掉,通常使用的方法就是在每次页面加载时,使用 javascript 遍历整个 dom 树,对于 dom 中所有的 a 和 form 标签后加入 token。这样可以解决大部分的请求,但是对于在页面加载之后动态生成的 html 代码,这种方法就没有作用,还需要程序员在编码时手动添加 token。</p>
<p>该方法还有一个缺点是难以保证 token 本身的安全。特别是在一些论坛之类支持用户自己发表内容的网站,黑客可以在上面发布自己个人网站的地址。由于系统也会在这个地址后面加上 token,黑客可以在自己的网站上得到这个 token,并马上就可以发动 CSRF 攻击。为了避免这一点,系统可以在添加 token 的时候增加一个判断,如果这个链接是链到自己本站的,就在后面添加 token,如果是通向外网则不加。不过,即使这个 csrftoken 不以参数的形式附加在请求之中,黑客的网站也同样可以通过 Referer 来得到这个 token 值以发动 CSRF 攻击。这也是一些用户喜欢手动关闭浏览器 Referer 功能的原因。</p>
<p><font color="#800000"><strong>在 HTTP 头中自定义属性并验证</strong></font></p>
<p>这种方法也是使用 token 并进行验证,和上一种方法不同的是,这里并不是把 token 以参数的形式置于 HTTP 请求之中,而是把它放到 HTTP 头中自定义的属性里。通过 XMLHttpRequest 这个类,可以一次性给所有该类请求加上 csrftoken 这个 HTTP 头属性,并把 token 值放入其中。这样解决了上种方法在请求中加入 token 的不便,同时,通过 XMLHttpRequest 请求的地址不会被记录到浏览器的地址栏,也不用担心 token 会透过 Referer 泄露到其他网站中去。</p>
<p>然而这种方法的局限性非常大。XMLHttpRequest 请求通常用于 Ajax 方法中对于页面局部的异步刷新,并非所有的请求都适合用这个类来发起,而且通过该类请求得到的页面不能被浏览器所记录下,从而进行前进,后退,刷新,收藏等操作,给用户带来不便。另外,对于没有进行 CSRF 防护的遗留系统来说,要采用这种方法来进行防护,要把所有请求都改为 XMLHttpRequest 请求,这样几乎是要重写整个网站,这代价无疑是不能接受的。</p>
<p><strong>CSRF的防御</strong></p>
<p>  我总结了一下看到的资料,CSRF的防御可以从服务端和客户端两方面着手,防御效果是从服务端着手效果比较好,现在一般的CSRF防御也都在服务端进行。这里给大家介绍一个开源项目,具体的内容可以参考作者的博客《Protecting ASP.NET Applications Against CSRF Attacks》。</p>
<p>1、通过Nuget 安装ARMOR Web Framework</p>
<p>PM&gt; Install-Package Daishi.Armor.WebFramework</p>
<p>2、添加几个配置项</p>
<div class="codeText">
<div class="codeHead"><span class="lantxt">C# Code</span><span style="CURSOR: pointer" class="copyCodeText" onclick="copyIdText('code_1465')">复制内容到剪贴板</span></div>
<div id="code_1465">
<ol class="dp-c">
    <li class="alt"><span><span>&lt;add&nbsp;key=&ldquo;IsArmed&rdquo;&nbsp;value=&ldquo;</span><span class="keyword">true</span><span>&rdquo;&nbsp;/&gt; &nbsp;&nbsp;</span></span></li>
    <li><span>&nbsp;&nbsp;</span></li>
    <li class="alt"><span>&lt;add&nbsp;key=&ldquo;ArmorEncryptionKey&rdquo;&nbsp;value=&ldquo;{Encryption&nbsp;Key}&rdquo;&nbsp;/&gt; &nbsp;&nbsp;</span></li>
    <li><span>&nbsp;&nbsp;</span></li>
    <li class="alt"><span>&lt;add&nbsp;key=&ldquo;ArmorHashKey&rdquo;&nbsp;value=&ldquo;{Hashing&nbsp;Key}&rdquo;&nbsp;/&gt; &nbsp;&nbsp;</span></li>
    <li><span>&nbsp;&nbsp;</span></li>
    <li class="alt"><span>&lt;add&nbsp;key=&ldquo;ArmorTimeout&rdquo;&nbsp;value=&ldquo;1200000&rdquo;&nbsp;/&gt; &nbsp;&nbsp;</span></li>
    <li><span>&nbsp;&nbsp;</span></li>
</ol>
</div>
</div>
<p>IsArmed: 打开或者关闭ARMOR 功能开关</p>
<p>ArmorEncryptionKey:ARMOR 的加密密钥,用于加密和解密Token</p>
<p>ArmorHashKey:用于生成和验证ARMOR 哈希值,包含在令牌内。ARMOR 用这个实现是否token被篡改过。</p>
<p>ArmorTimeout:以毫秒为单位的ARMOR 的token有效期</p>
<p>可以用下面的这段代码去生成</p>
<div class="codeText">
<div class="codeHead"><span class="lantxt">C# Code</span><span style="CURSOR: pointer" class="copyCodeText" onclick="copyIdText('code_6677')">复制内容到剪贴板</span></div>
<div id="code_6677">
<ol class="dp-c">
    <li class="alt"><span><span class="keyword">byte</span><span>[]&nbsp;encryptionKey&nbsp;=&nbsp;</span><span class="keyword">new</span><span>&nbsp;</span><span class="keyword">byte</span><span>; &nbsp;&nbsp;</span></span></li>
    <li><span>&nbsp;&nbsp;</span></li>
    <li class="alt"><span></span><span class="keyword">byte</span><span>[]&nbsp;hashingKey&nbsp;=&nbsp;</span><span class="keyword">new</span><span>&nbsp;</span><span class="keyword">byte</span><span>; &nbsp;&nbsp;</span></li>
    <li><span>&nbsp;&nbsp;</span></li>
    <li class="alt"><span></span><span class="keyword">using</span><span>&nbsp;(var&nbsp;provider&nbsp;=&nbsp;</span><span class="keyword">new</span><span>&nbsp;RNGCryptoServiceProvider())&nbsp;{ &nbsp;&nbsp;</span></li>
    <li><span>&nbsp;&nbsp;</span></li>
    <li class="alt"><span>provider.GetBytes(encryptionKey); &nbsp;&nbsp;</span></li>
    <li><span>&nbsp;&nbsp;</span></li>
    <li class="alt"><span>provider.GetBytes(hashingKey); &nbsp;&nbsp;</span></li>
    <li><span>&nbsp;&nbsp;</span></li>
</ol>
</div>
</div>
<p>3、应用程序中加入 一个ARMOR 钩子</p>
<p>以上就是防止CSRF攻击ASP.NET 应用的方法,希望大家喜欢。</p>
頁: [1]
查看完整版本: 防止CSRF攻击ASP.NET应用