XSS 前端防火墙 —— 天衣无缝的防护
发布在邹菜头杂谈2014年6月17日view:2069
在文章任何区域双击击即可给文章添加【评注】!浮到评注点上可以查看详情。

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

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

例如,我们的属性钩子只考虑了 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 也就足够了。

可疑模块拦截

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

可执行元素

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

脚本

<script src="..." /> 框架