HTML5技术

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

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

script type="text/javascript" src="./xss/a.js"/script是页面加载一开始就存在的静态脚本(查看页面结构),我们使用 MutationObserver 可以在脚本加载之后,执行之前这个时间段对其内容做正则匹配,发现恶意代码

<script type="text/javascript" src="./xss/a.js"></script> 是页面加载一开始就存在的静态脚本(查看页面结构),我们使用 MutationObserver 可以在脚本加载之后,执行之前这个时间段对其内容做正则匹配,发现恶意代码则 removeChild() 掉,使之无法执行。

  使用白名单对 src 进行匹配过滤

上面的代码中,我们判断一个js脚本是否是恶意的,用的是这一句:

if (/xss/i.test(node.src)) {}

当然实际当中,注入恶意代码者不会那么傻,把名字改成 XSS 。所以,我们很有必要使用白名单进行过滤和建立一个拦截上报系统。 

// 建立白名单 var whiteList = [ 'www.aaa.com', 'res.bbb.com' ]; /** * [白名单匹配] * @param {[Array]} whileList [白名单] * @param {[String]} value [需要验证的字符串] * @return {[Boolean]} [false -- 验证不通过,true -- 验证通过] */ function whileListMatch(whileList, value) { var length = whileList.length, i = 0; for (; i < length; i++) { // 建立白名单正则 var reg = new RegExp(whiteList[i], 'i'); // 存在白名单中,放行 if (reg.test(value)) { return true; } } return false; } // 只放行白名单 if (!whileListMatch(blackList, node.src)) { node.parentNode.removeChild(node); } 

这里我们已经多次提到白名单匹配了,下文还会用到,所以可以这里把它简单封装成一个方法调用。

  动态脚本拦截

上面使用 MutationObserver 拦截静态脚本,除了静态脚本,与之对应的就是动态生成的脚本。

var script = document.createElement('script'); script.type = 'text/javascript'; script.src = 'http://www.example.com/xss/b.js'; document.getElementsByTagName('body')[0].appendChild(script); 

要拦截这类动态生成的脚本,且拦截时机要在它插入 DOM 树中,执行之前,本来是可以监听 Mutation Events 中的 DOMNodeInserted 事件的。

  Mutation Events 与 DOMNodeInserted

打开 MDN ,第一句就是:

该特性已经从 Web 标准中删除,虽然一些浏览器目前仍然支持它,但也许会在未来的某个时间停止支持,请尽量不要使用该特性。

虽然不能用,也可以了解一下:

document.addEventListener('DOMNodeInserted', function(e) { var node = e.target; if (/xss/i.test(node.src) || /xss/i.test(node.innerHTML)) { node.parentNode.removeChild(node); console.log('拦截可疑动态脚本:', node); } }, true);

然而可惜的是,使用上面的代码拦截动态生成的脚本,可以拦截到,但是代码也执行了:DOMNodeInserted 顾名思义,可以监听某个 DOM 范围内的结构变化,与 MutationObserver 相比,它的执行时机更早。

但是 DOMNodeInserted 不再建议使用,所以监听动态脚本的任务也要交给 MutationObserver。

可惜的是,在实际实践过程中,使用 MutationObserver 的结果和 DOMNodeInserted 一样,可以监听拦截到动态脚本的生成,但是无法在脚本执行之前,使用 removeChild 将其移除,所以我们还需要想想其他办法。

  重写 setAttribute 与 document.write

重写原生 Element.prototype.setAttribute 方法

在动态脚本插入执行前,监听 DOM 树的变化拦截它行不通,脚本仍然会执行。

那么我们需要向上寻找,在脚本插入 DOM 树前的捕获它,那就是创建脚本时这个时机。

假设现在有一个动态脚本是这样创建的:

var script = document.createElement('script'); script.setAttribute('type', 'text/javascript'); script.setAttribute('src', 'http://www.example.com/xss/c.js'); document.getElementsByTagName('body')[0].appendChild(script);

而重写 Element.prototype.setAttribute 也是可行的:我们发现这里用到了 setAttribute 方法,如果我们能够改写这个原生方法,监听设置 src 属性时的值,通过黑名单或者白名单判断它,就可以判断该标签的合法性了。

// 保存原有接口 var old_setAttribute = Element.prototype.setAttribute; // 重写 setAttribute 接口 Element.prototype.setAttribute = function(name, value) { // 匹配到 <script src='xxx' > 类型 if (this.tagName == 'SCRIPT' && /^src$/i.test(name)) { // 白名单匹配 if (!whileListMatch(whiteList, value)) { console.log('拦截可疑模块:', value); return; } } // 调用原始接口 old_setAttribute.apply(this, arguments); }; // 建立白名单 var whiteList = [ 'www.yy.com', 'res.cont.yy.com' ]; /** * [白名单匹配] * @param {[Array]} whileList [白名单] * @param {[String]} value [需要验证的字符串] * @return {[Boolean]} [false -- 验证不通过,true -- 验证通过] */ function whileListMatch(whileList, value) { var length = whileList.length, i = 0; for (; i < length; i++) { // 建立白名单正则 var reg = new RegExp(whiteList[i], 'i'); // 存在白名单中,放行 if (reg.test(value)) { return true; } } return false; }

可以看到如下结果:可以戳我查看DEMO。(打开页面后打开控制台查看 console.log)

 

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

网友点评