移动安全 安全管理 应用案例 网络威胁 系统安全应用安全 数据安全 云安全
当前位置: 主页 > 信息安全 > 应用安全 >

针对XSS漏洞的前端防火墙:天衣无缝的防护

时间:2014-06-27 16:35来源:TuZhiJiaMi企业信息安全专家 点击:
上一篇讲解了钩子程序的攻防实战,并实现了一套对框架页的监控方案,将防护作用到所有子页面。 到目前为止,我们防护的深度已经差不多,但广度还有所欠缺。 例如,我们的属性钩子只考
Tags漏洞(188)XSS(21)应用安全(1006)防火墙(206)  

  上一篇讲解了钩子程序的攻防实战,并实现了一套对框架页的监控方案,将防护作用到所有子页面。

  到目前为止,我们防护的深度已经差不多,但广度还有所欠缺。

  例如,我们的属性钩子只考虑了setAttribute,却忽视还有类似的setAttributeNode。尽管从来不用这方法,但并不意味人家不能使用。

  例如,创建元素通常都是createElement,事实上createElementNS同样也可以。甚至还可以利用现成的元素cloneNode,也能达到目的。因此,这些都是边缘方法都是值得考虑的。

  下面我们对之前讨论过的监控点,进行逐一审核。

  内联事件执行 eval

  在第一篇文章结尾谈到,在执行回调的时候,最好能监控eval,setTimeout('...') 这些能够解析代码的函数,以防止执行储存在其他地方的XSS代码。

  先来列举下这类函数:

  eval

  setTimeout(String) / setInterval(String)

  Function

  execScript / setImmediate(String)

  事实上,利用上一篇的钩子技术,完全可以把它们都监控起来。但现实并没有我们想象的那样简单。

  eval 重写有问题吗

  eval 不就是个函数,为什么不可以重写?

  var raw_fn = window.eval; window.eval = function(exp) { alert('执行eval: ' + exp); return raw_fn.apply(this, arguments); }; console.log(eval('1+1'));

  完全没问题啊。那是因为代码太简单了,下面这个 Demo 就可以看出山寨版 eval 的缺陷:

  (function() { eval('var a=1'); })(); alert(typeof a);

  Run

  按理说应该 undefined 才对,结果却是 number。局部变量都跑到全局上来了。这是什么情况?事实上,eval 并不是真正意义的函数,而是一个关键字!

  Function 重写有意义吗

  Function 是一个全局变量,重写 window.Function 理论上完全可行吧。

  var raw_fn = window.Function; window.Function = function() { alert('调用Function'); return raw_fn.apply(this, arguments); }; var add = Function('a', 'b', 'return a+b'); console.log( add(1, 2) );

  重写确实可行。但现实却是不堪一击的:因为所有函数都是 Function 类的实例,所以访问任何一个函数的 constructor 即可得到原始的 Function。

  例如 alert.constructor,就可以绕过我们的钩子。甚至可以用匿名函数:

  (function(){}).constructor

  所以,Function 是永远钩不住的。

  额外的执行方法

  就算不用这类函数,仍有相当多的办法执行字符串,例如:

  创建脚本,innerHTML = 代码

  创建脚本,路径 = data:代码

  创建框架,路径 = javascript:代码

  ......

  看来,想完全把类似 eval 的行为监控起来,是不现实的。不过作为预警,我们只监控 eval,setTimeout/Interval 也就足够了。

  可疑模块拦截

  第二篇谈了站外模块的拦截。之所以称之『模块』而不是『脚本』,并非只有脚本元素才具备执行能力。框架页、插件都是可以运行代码的。

  可执行元素

  我们列举下,能执行远程模块的元素:

  脚本

  

  Run

  尽管会有瞬间的闪动,但从新窗口里获取的变量确实被保留下来了,并且依然起作用。因为我们引用着它,所以即使窗口关闭,仍然不会对其内存回收的。

  现实中,可以把点击事件绑在 document 上,这样用户随便点哪里都能触发,以此获得纯净的环境。

  因此,我们还得把弹窗函数,也通过钩子保护起来。

  除了最常用的 window.open,其实还有:

  showModalDialog

  showModelessDialog

  opener

  如果当前网页是从其他页面点击打开的,无论是弹窗还是超链接,window.opener 都记录着来源页的环境。

  如果是来源页和自己又是同源站点,甚至还能访问到来源页里面的变量。

  这种情况相当常见。例如从帖子列表页,点开一个帖子详情页,那么详情页是完全可以操控列表页的。

  要解决这个问题也不难,直接给 window.opener 注入防护程序不就可以了,就像对待新出现的框架页那样。

  但是,window.opener 可能也有自己的 opener,一层层递归上去或许有很多。每个页面也许又有自己的框架页,因此防护 window.opener 可能会执行非常多的代码。如果在初始化时就进行,或许会有性能问题。

  事实上,这个冷门的属性几乎不怎么用到。所以不如做个延时策略:只有第一次访问 opener 的时候,才对其进行防护。

  我们将 window.opener 进行重写,把它变成一个 getter 访问器:

  var raw_opener = window.opener; var scanned; window.__defineGetter__('opener', function() { if (!scanned) { installHook(raw_opener); scanned = true; } return raw_opener; });

  这样,只要不访问 opener,就不会触发对它的防护,做到真正按需执行。

  后记

  关于防护监控点,也没有一个完整的答案,能想到多少算多少,以后可以慢慢补充。

  但是,装了那么多的钩子及事件,对页面的性能影响有多大呢?

  所以,我们还得开发一个测试控制台,来跟踪这套系统。看看监控全开时,会对页面产生多大影响。

------分隔线----------------------------

推荐内容