重写 Element.prototype.setAttribute ,就是首先保存原有接口,然后当有元素调用 setAttribute 时,检查传入的 src 是否存在于白名单中,存在则放行,不存在则视为可疑元素,进行上报并不予以执行。最后对放行的元素执行原生的 setAttribute ,也就是 old_setAttribute.apply(this, arguments);。
上述的白名单匹配也可以换成黑名单匹配。
重写嵌套 iframe 内的 Element.prototype.setAttribute
当然,上面的写法如果 old_setAttribute = Element.prototype.setAttribute 暴露给攻击者的话,直接使用old_setAttribute 就可以绕过我们重写的方法了,所以这段代码必须包在一个闭包内。
当然这样也不保险,虽然当前窗口下的 Element.prototype.setAttribute 已经被重写了。但是还是有手段可以拿到原生的 Element.prototype.setAttribute ,只需要一个新的 iframe 。
var newIframe = document.createElement('iframe'); document.body.appendChild(newIframe); Element.prototype.setAttribute = newIframe.contentWindow.Element.prototype.setAttribute;
通过这个方法,可以重新拿到原生的 Element.prototype.setAttribute ,因为 iframe 内的环境和外层 window 是完全隔离的。wtf?
怎么办?我们看到创建 iframe 用到了 createElement,那么是否可以重写原生 createElement 呢?但是除了createElement 还有 createElementNS ,还有可能是页面上已经存在 iframe,所以不合适。
那就在每当新创建一个新 iframe 时,对 setAttribute 进行保护重写,这里又有用到 MutationObserver :
/** * 使用 MutationObserver 对生成的 iframe 页面进行监控, * 防止调用内部原生 setAttribute 及 document.write * @return {[type]} [description] */ function defenseIframe() { // 先保护当前页面 installHook(window); } /** * 实现单个 window 窗口的 setAttribute保护 * @param {[BOM]} window [浏览器window对象] * @return {[type]} [description] */ function installHook(window) { // 重写单个 window 窗口的 setAttribute 属性 resetSetAttribute(window); // MutationObserver 的不同兼容性写法 var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; // 该构造函数用来实例化一个新的 Mutation 观察者对象 // Mutation 观察者对象能监听在某个范围内的 DOM 树变化 var observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { // 返回被添加的节点,或者为null. var nodes = mutation.addedNodes; // 逐个遍历 for (var i = 0; i < nodes.length; i++) { var node = nodes[i]; // 给生成的 iframe 里环境也装上重写的钩子 if (node.tagName == 'IFRAME') { installHook(node.contentWindow); } } }); }); observer.observe(document, { subtree: true, childList: true }); } /** * 重写单个 window 窗口的 setAttribute 属性 * @param {[BOM]} window [浏览器window对象] * @return {[type]} [description] */ function resetSetAttribute(window) { // 保存原有接口 var old_setAttribute = window.Element.prototype.setAttribute; // 重写 setAttribute 接口 window.Element.prototype.setAttribute = function(name, value) { ... }; }
我们定义了一个 installHook 方法,参数是一个 window ,在这个方法里,我们将重写传入的 window 下的 setAttribute ,并且安装一个 MutationObserver ,并对此窗口下未来可能创建的 iframe 进行监听,如果未来在此 window 下创建了一个 iframe ,则对新的 iframe 也装上 installHook 方法,以此进行层层保护。
重写 document.write
根据上述的方法,我们可以继续挖掘一下,还有什么方法可以重写,以便对页面进行更好的保护。
document.write 是一个很不错选择,注入攻击者,通常会使用这个方法,往页面上注入一些弹窗广告。
我们可以重写 document.write ,使用关键词黑名单对内容进行匹配。
什么比较适合当黑名单的关键字呢?我们可以看看一些广告很多的页面: