性能优化
或许有些事件没有必要捕获,例如视频播放、音量调节等,但就算全都捕捉也耗不了多少时间,基本都在 1ms 左右。
当然,注册事件本来就花不了多少时间,真正的耗费都算在回调上了。尽管大多数事件触发都不频繁,额外的扫描可以忽律不计。但和鼠标移动相关的事件那就不容忽视了,因此得考虑性能优化。
显然,内联事件代码在运行过程中几乎不可能发生变化。使用内联事件大多为了简单,如果还要在运行时 setAttribute 去改变内联代码,完全就是不可理喻的。因此,我们只需对某个元素的特定事件,扫描一次就可以了。之后根据标志,即可直接跳过。
<div style="width:100%; height:100%; position:absolute" onmouseover="console.log('xss')"></div> <script> function hookEvent(onevent) { document.addEventListener(onevent.substr(2), function(e) { var element = e.target; // 跳过已扫描的事件 var flags = element['_flag']; if (!flags) { flags = element['_flag'] = {}; } if (typeof flags[onevent] != 'undefined') { return; } flags[onevent] = true; if (element.nodeType != Node.ELEMENT_NODE) { return; } var code = element.getAttribute(onevent); if (code && /xss/.test(code)) { element[onevent] = null; console.log('拦截可疑代码:', code); } }, true); } for (var k in document) { if (/^on/.test(k)) { hookEvent(k); } } </script> Run |
这样,之后的扫描仅仅是判断一下目标对象中的标记而已。即使疯狂晃动鼠标,CPU 使用率也都忽略不计了。
到此,在 XSS 内联事件这块,我们已实现主动防御。
对于有着大量字符,或者出现类似 String.fromCharCode,$.getScript 这类典型 XSS 代码的,完全可以将其拦截;发现有 alert(/xss/),alert(123) 这些测试代码,可以暂时放行,并将日志发送到后台,确定是否能够复现。
如果复现,说明已有人发现 XSS 并成功注入了,但还没大规模开始利用。程序猿们赶紧第一时间修 BUG 吧,让黑客忙活一阵子后发现漏洞已经修复了:)
字符策略的缺陷
但是,光靠代码字符串来判断,还是会有疏漏的。尤其是黑客们知道有这么个玩意存在,会更加小心了。把代码转义用以躲避关键字,并将字符存储在其他地方,以躲过长度检测,即可完全绕过我们的监控了:
<img src="*" onerror="window['ev'+'al'](this.align)" align="alert('a mass of code...')">
因此,我们不仅需要分析关键字。在回调执行时,还需监控 eval、setTimeout('...') 等这类能解析代码的函数被调用。
不过,通常不会注入太多的代码,而是直接引入一个外部脚本,既简单又靠谱,并且能实时修改攻击内容:
<img src="*" onerror="$['get'+'Script'](...)">