HTML5技术

【前端安全】JavaScript防http劫持与XSS - ChokCoco(5)

字号+ 作者:H5之家 来源:博客园 2016-08-19 17:00 我要评论( )

这里在页面最底部嵌入了一个 iframe ,里面装了广告代码,这里的最外层的 id 名id="BAIDU_SSP__wrapper_u2444091_0"就很适合成为我们判断是否是恶意代码的一个标志,假设我们已经根据拦截上报收集到了一批黑名单列

这里在页面最底部嵌入了一个 iframe ,里面装了广告代码,这里的最外层的 id 名id="BAIDU_SSP__wrapper_u2444091_0" 就很适合成为我们判断是否是恶意代码的一个标志,假设我们已经根据拦截上报收集到了一批黑名单列表:

// 建立正则拦截关键词 var keywordBlackList = [ 'xss', 'BAIDU_SSP__wrapper', 'BAIDU_DSPUI_FLOWBAR' ];

接下来我们只需要利用这些关键字,对 document.write 传入的内容进行正则判断,就能确定是否要拦截document.write 这段代码。 

```javascript // 建立关键词黑名单 var keywordBlackList = [ 'xss', 'BAIDU_SSP__wrapper', 'BAIDU_DSPUI_FLOWBAR' ]; /** * 重写单个 window 窗口的 document.write 属性 * @param {[BOM]} window [浏览器window对象] * @return {[type]} [description] */ function resetDocumentWrite(window) { var old_write = window.document.write; window.document.write = function(string) { if (blackListMatch(keywordBlackList, string)) { console.log('拦截可疑模块:', string); return; } // 调用原始接口 old_write.apply(document, arguments); } } /** * [黑名单匹配] * @param {[Array]} blackList [黑名单] * @param {[String]} value [需要验证的字符串] * @return {[Boolean]} [false -- 验证不通过,true -- 验证通过] */ function blackListMatch(blackList, value) { var length = blackList.length, i = 0; for (; i < length; i++) { // 建立黑名单正则 var reg = new RegExp(whiteList[i], 'i'); // 存在黑名单中,拦截 if (reg.test(value)) { return true; } } return false; } 

我们可以把 resetDocumentWrite 放入上文的 installHook 方法中,就能对当前 window 及所有生成的 iframe 环境内的 document.write 进行重写了。

  锁死 apply 和 call

接下来要介绍的这个是锁住原生的 Function.prototype.apply 和 Function.prototype.call 方法,锁住的意思就是使之无法被重写。

这里要用到 Object.defineProperty ,用于锁死 apply 和 call。

  Object.defineProperty

Object.defineProperty() 方法直接在一个对象上定义一个新属性,或者修改一个已经存在的属性, 并返回这个对象。

Object.defineProperty(obj, prop, descriptor)

其中: 

我们可以使用如下的代码,让 call 和 apply 无法被重写。

// 锁住 call Object.defineProperty(Function.prototype, 'call', { value: Function.prototype.call, // 当且仅当仅当该属性的 writable 为 true 时,该属性才能被赋值运算符改变 writable: false, // 当且仅当该属性的 configurable 为 true 时,该属性才能够被改变,也能够被删除 configurable: false, enumerable: true }); // 锁住 apply Object.defineProperty(Function.prototype, 'apply', { value: Function.prototype.apply, writable: false, configurable: false, enumerable: true }); 

为啥要这样写呢?其实还是与上文的 重写 setAttribute 有关。

虽然我们将原始 Element.prototype.setAttribute 保存在了一个闭包当中,但是还有奇技淫巧可以把它从闭包中给“偷出来”。

试一下:

(function() {})( // 保存原有接口 var old_setAttribute = Element.prototype.setAttribute; // 重写 setAttribute 接口 Element.prototype.setAttribute = function(name, value) { // 具体细节 if (this.tagName == 'SCRIPT' && /^src$/i.test(name)) {} // 调用原始接口 old_setAttribute.apply(this, arguments); }; )(); // 重写 apply Function.prototype.apply = function(){ console.log(this); } // 调用 setAttribute document.getElementsByTagName('body')[0].setAttribute('data-test','123'); 

猜猜上面一段会输出什么?看看:

居然返回了原生 setAttribute 方法!

这是因为我们在重写 Element.prototype.setAttribute 时最后有 old_setAttribute.apply(this, arguments);这一句,使用到了 apply 方法,所以我们再重写 apply ,输出 this ,当调用被重写后的 setAttribute 就可以从中反向拿到原生的被保存起来的 old_setAttribute 了。

这样我们上面所做的嵌套 iframe 重写 setAttribute 就毫无意义了。

使用上面的 Object.defineProperty 可以锁死 apply 和 类似用法的 call 。使之无法被重写,那么也就无法从闭包中将我们的原生接口偷出来。这个时候才算真正意义上的成功重写了我们想重写的属性。

  建立拦截上报

防御的手段也有一些了,接下来我们要建立一个上报系统,替换上文中的 console.log() 日志。

上报系统有什么用呢?因为我们用到了白名单,关键字黑名单,这些数据都需要不断的丰富,靠的就是上报系统,将每次拦截的信息传到服务器,不仅可以让我们程序员第一时间得知攻击的发生,更可以让我们不断收集这类相关信息以便更好的应对。

这里的示例我用 nodejs 搭一个十分简易的服务器接受 http 上报请求。

先定义一个上报函数:

 

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

相关文章
  • web前端之HTML中元素的区分 - 紫竹六月

    web前端之HTML中元素的区分 - 紫竹六月

    2016-08-18 16:00

  • web前端之HTML的大框架(body元素与frameset元素) - 紫竹六月

    web前端之HTML的大框架(body元素与frameset元素) - 紫竹六月

    2016-08-14 17:00

  • CSS3 时钟 - 前端爱好者

    CSS3 时钟 - 前端爱好者

    2016-08-14 10:01

  • 移动端UC /QQ 浏览器的部分私有Meta 属性 - 前端小超人

    移动端UC /QQ 浏览器的部分私有Meta 属性 - 前端小超人

    2016-07-29 15:00

网友点评