首页 网络安全 安全学院 查看内容

御CSRF的第四种方法

2010-3-24 17:46 861 0

摘要:     防御CSRF的第四种方法     我们知道,一般防御CSRF有三种方法,判断referer、验证码、token。  &...
关键词: nbsp cookies 子域 表单 方法 防御 用户 token 如果 本域

    防御CSRF的第四种方法     我们知道,一般防御CSRF有三种方法,判断referer、验证码、token。     对于判断referer来说,虽然客户端带用户状态的跨域提交,js和as已经无法伪造referer了;但是对于客户端软件和flash的提交,一般是不带referer的,据说一些山寨浏览器也不带。那么就需要为此开绿灯,但这样使得外站的flash请求伪造无法被防御。     而验证码弊端明显:会对用户造成影响。     token的问题也有一些:时效性无法保证;大型服务时,需要一台token生成及校验服务器;需要更改所有表单添加字段。     而最近我在做之类的防御时,想出了另外一种方法,跟xeye、woyigui等人在群里讨论了一番,认为应该是可行的,所以拿出来分享一下,并让其他的牛人看看是否有什么弊端     其实原理非常简单,与token也差不多:当表单提交时,用js在本域添加一个临时的cookies字段,并将过期时间设为1秒钟之后,然后再提交;服务端校验有这个字段即放行,没有则认为是CSRF。     token防csrf的原理是:无法通过ajax等方式获得外域页面中的token值,xmlhttprequest需要遵守浏览器同源策略;而临时cookies的原理也是:cookies只能在父域和子域之间设置,也遵守同源策略。     我们可以简单看一个demo:     demo:     http://127.0.0.1/test.html:     <script>     function doit(){     var expires = new Date((new Date()).getTime()+1000);     document.cookie = "xeye=xeye; expires=" + expires.toGMTString();     }     </script>     <form action="http://127.0.0.1/test.php" name="f" id="f" onsubmit="doit();" target="if1">     <input type="button" value="normal submit" onclick="f.submit();">     <input type="button" value="with token" onclick="doit();f.submit();">     <input type="submit" value="hook submit">     </form>     <iframe src="about:blank" name="if1" id="if1"></iframe>     http://127.0.0.1/test.php     <?php     echo "<div>Cookies</div>";     var_dump($_COOKIE);     ?>     test.html为浏览器端的表单,里面有三个按钮:     第一个是正常的表单提交;第二个是添加临时cookies后提交表单;第三个是以hook submit事件来添加临时cookies并提交。     结果就像开头的图片演示那样,正常的表单提交不会出现临时cookies字段,第二个和第三个按钮提交则会出现。大家可以反复点击按钮来查看结果,但需要注意时间间隔需超过1秒。(当然可以将test.html拿到外域看看,不过要注意form的target不能指向iframe了,可以以新窗口打开。由于同源策略,cookies肯定是带不过去的)     不过这种方式只适用于单域名站点,或者安全需求不需要“当子域发生XSS隔离父域”。因为子域是可以操作父域的cookies的,所以它的缺陷也比较明显:这种方法无法防御由于其他子域产生的xss所进行的表单伪造提交。而一个区分分域的自校验token是可以防止从其他子域到本域的提交的。但如果对于单域而言,这种方法应该是足够的,并且安全性可能会略大于token。     和群里的几位大牛讨论了一下,也认为这种方式没有什么大问题,不过确实有一些小的疑问,譬如:     网络不流畅,有延迟会不会导致cookies失效。这个显然是不会的,因为服务端cookies是在提交请求的header中获得的。延时在服务端,不在客户端,而1秒钟足可以完成set Cookies+post header整个post表单的过程。     cookies的生成依赖于js,相当于这个token是明文的?这个的确,不管采取多少种加密,只要在客户端,就会被破解,不过不管怎样,csrf无法在有用户状态的情况下去添加这个临时cookies字段。虽然服务端curl等可以,但是无法将当前用户的状态也带过去。     外站是否可以伪造这个临时cookies呢?目前来看至少通过as和js无法向其他域添加和更改cookies的,通过服务端虽然可以伪造cookies,但获得不到目标域的用户状态。     如果目标域有XSS就完蛋了?恩,不过一般来说判断referer、token和简单的验证码(利用canvas识别?)也差不多完蛋了。     如果由于某种网络问题,获得不到cookies了呢?那么用户状态也不能获得了,用户只能再提交一次了。     ok,就这些!     说实话,这种新方法究竟是否真正有效我也没谱,说不定有某种BT的方式可以绕过?所以share一下,大家不妨看看是否真的有效。如果真有效,那么大概是一种最简单的,对代码改动最小,对服务器压力也最小的防御CSRF的方法了。     很巧妙的想法,但是没法100%保证不被攻击,这个方法的安全性依赖于cookie的失效时间,假设有个csrf页面里死循环不停提交的话,如果被攻击用户打开这个页面没有关闭,这个过程中如果用户又去被攻击的网站作一次正常操作的话,攻击就有一定的概率成功了,比如以下代码在ff中就有一定的概率成功(ie不行,因为iframe跨域提交cookie被阻止了):     <html>     <body>     <iframe name="iframe1"></iframe>     <form id="form1" action="http://127.0.0.1/test.php" method="post" target="iframe1">     <input type="submit" />     </form>     <script>     setInterval(function() {     var form1 = document.getElementById("form1");     form1.submit();     }, 500);     </script>     </body>     </html>     所以如果攻击者使受害者在FF下不断刷页面并且小于等于500毫秒,那么显然这种方式就失效了。由于浏览器限制,我们不能将cookies的失效时间改成比1秒更小。但我们可以提交完立即就清除cookies,由于submit事件没有返回,所以我们依然只能用延时来搞定。     所以我们将服务端的test.php改成:     <?php     echo "<div>Cookies</div>";     var_dump($_COOKIE);     if(isset($_COOKIE["xeye"])){     echo "<script>try{top.document} catch(ex){alert(1);}</script>";     }     ?>     这样当外域加载且被设置cookies时,就会弹窗口,本域就不会。     将客户端的改成:     <script>     function doit(){     var expires = new Date((new Date()).getTime()+2000);     document.cookie = "xeye=xeye; expires=" + expires.toGMTString();     f.submit();     setTimeout(function(){     var expires = new Date((new Date()).getTime()-2000);     document.cookie = "xeye=xeye; expires=" + expires.toGMTString();     },1);     return false;     }     </script>     <form action="http://127.0.0.1/test.php" name="f" id="f" onsubmit="return doit();" target="if1">     <input type="button" value="normal submit" onclick="f.submit();">     <input type="button" value="with token" onclick="doit();">     <input type="submit" value="hook submit">     </form>     <iframe src="about:blank" name="if1" id="if1"></iframe>     通过测试发现,IE和chrome是没有任何问题的!不过FF偶尔还是弹出窗口,其原因在于它处理setTimeout居然有延时,并且应该是在0.5~1秒钟左右,巨汗,不过概率已经小一些了!     并且在测试中发现FF对按钮的onclick事件后的代码执行顺序与onsubmit的代码执行顺序居然不一样,onclick的事件中可以先set cookies、再submit、再清cookies;但onsubmit居然是先set cookies、再清cookies、再submit,服了。所以通过非常蹩脚的方法,我们可以将概率改成更小,但依旧不完美:     <script>     function doit(submit){     var expires = new Date((new Date()).getTime()+2000);     document.cookie = "xeye=xeye; expires=" + expires.toGMTString();     f.submit();     if(/a/[-1]=='a' && !submit){ //FF并且不是onsubmit事件     var expires = new Date((new Date()).getTime()-2000);     document.cookie = "xeye=xeye; expires=" + expires.toGMTString();     }else{     setTimeout(function(){     var expires = new Date((new Date()).getTime()-2000);     document.cookie = "xeye=xeye; expires=" + expires.toGMTString();     },1);     }     return false;     }     </script>     <form action="http://127.0.0.1/test.php" name="f" id="f" onsubmit="return doit(true);" target="if1">     <input type="button" value="normal submit" onclick="f.submit();">     <input type="button" value="with token" onclick="doit(false);">     <input type="submit" value="hook submit">     </form>     <iframe src="about:blank" name="if1" id="if1"></iframe>     因为如果用onsubmit事件触发,则依旧有一点的概率使得防御被绕过!     不知各位大牛是否有什么针对FF的JS技巧使得代码可以先set cookies、再submit、再unset cookies这样的顺序执行,而不是异步?这样如果不用setTimeout,那么问题就可以解决了!
声明:文章版权归原作者所有 部分文章转自互联网 如有侵权请联系 [邮箱地址] 删除

路过

雷人

握手

鲜花

鸡蛋

最新评论

返回顶部