来自 奥门威尼斯网址 2019-09-21 03:38 的文章
当前位置: 威尼斯国际官方网站 > 奥门威尼斯网址 > 正文

前端安全

JavaScript 防 http 劫持与 XSS

2016/08/17 · JavaScript · 1 评论 · http劫持, X DNS劫持, XSS, 安全

本文作者: 伯乐在线 - chokcoco 。未经小编许可,禁止转发!
应接参预伯乐在线 专栏撰稿人。

用作前端,长久以来都知情HTTP劫持XSS跨站脚本(Cross-site scripting)、CSRF跨站请求伪造(Cross-site request forgery)。可是一向都未曾深切钻探过,前段时间同事的分享会不经常聊到,作者也对这一块很感兴趣,便浓密切磋了一番。

新近用 JavaScript 写了一个零件,可以在后面一个层面防范部分 HTTP 威逼与 XSS。

自然,堤防这一个威逼最佳的点子还是从后端动手,前端能做的实在太少。並且由于源码的展露,攻击者很轻巧绕过咱们的防止手腕。不过那不代表大家去打听那块的有关文化是没意义的,本文的洋洋主意,用在其余方面也是大有效能。

已上盛传 Github – httphijack.js ,应接感兴趣看看顺手点个 star ,本文示例代码,防备措施在组件源码中皆可找到。

接下去步向正文。

作为前端,一如既往都精通HTTP劫持XSS跨站脚本(Cross-site scripting)、CSRF跨站请求伪造(Cross-site request forgery)。不过一贯都并未有深入钻研过,后三个月同事的分享会有的时候说到,小编也对这一块很感兴趣,便深切钻研了一番。

HTTP劫持、DNS劫持与XSS

先轻便讲讲什么样是 HTTP 勒迫与 DNS 威迫。

新近用 JavaScript 写了一个零件,能够在前面二个层面防范部分 HTTP 恐吓与 XSS。

HTTP劫持

何以是HTTP威胁呢,大非常多气象是营业商HTTP威迫,当大家采纳HTTP央求央浼贰个网址页面包车型地铁时候,互联网运维商会在正规的多寡流中插入精心设计的网络数据报文,让顾客端(平常是浏览器)呈现“错误”的数量,日常是有的弹窗,宣传性广告依然直接体现某网址的内容,大家应该都有相逢过。

本来,防范那几个胁制最棒的不二秘技照旧从后端入手,前端能做的实在太少。并且由于源码的暴光,攻击者很轻易绕过大家的防卫手腕。不过那不代表我们去询问那块的相干知识是没意义的,本文的相当多措施,用在别的方面也是大有效率。

DNS劫持

DNS威吓就是通过勒迫了DNS服务器,通过有些花招获取某域名的解析记录调整权,进而修改此域名的剖析结果,导致对该域名的拜访由原IP地址转入到修改后的钦点IP,其结果正是对一定的网站无法访问或访谈的是假网站,进而达成窃取资料照旧破坏原有正平常衣服务的目标。

DNS 威迫就更过分了,轻便说就是大家恳请的是  ,直接被重定向了 ,本文不会过多商讨这种景观。

已上盛传 Github – httphijack.js ,应接感兴趣看看顺手点个 star ,本文示例代码,堤防措施在组件源码中皆可找到。

XSS跨站脚本

XSS指的是攻击者漏洞,向 Web 页面中流入恶意代码,当客商浏览该页之时,注入的代码会被实施,从而达到攻击的非正规指标。

关于那个攻击怎么着变迁,攻击者怎么着注入恶意代码到页面中本文不做研究,只要理解如 HTTP 勒迫 和 XSS 最后都是恶意代码在顾客端,平时也正是顾客浏览器端试行,本文将切磋的就是若是注入已经存在,怎么样选取Javascript 举办有效的前端防护。

接下去步入正文。

页面被置于 iframe 中,重定向 iframe

先来讲说我们的页面被放置了 iframe 的意况。也等于,网络运营商为了尽量地压缩植入广告对原来网址页面包车型地铁熏陶,平日会通过把本来网址页面放置到一个和原页面同样大小的 iframe 里面去,那么就能够透过那么些 iframe 来隔绝广告代码对原本页面的熏陶。
奥门威尼斯网址 1

这种情景还比较好处理,大家只须求掌握大家的页面是或不是被嵌套在 iframe 中,假诺是,则重定向外层页面到我们的正规页面就可以。

那么有没有主意知情大家的页面当前留存于 iframe 中呢?有的,正是 window.self 与 window.top 。

 

window.self

归来多少个对准当前 window 对象的引用。

HTTP劫持、DNS劫持与XSS

先简单讲讲如何是 HTTP 吓唬与 DNS 威逼。

window.top

归来窗口类别中的最顶层窗口的援用。

对此非同源的域名,iframe 子页面不可能通过 parent.location 可能top.location 获得具体的页面地址,可是足以写入 top.location ,也便是能够调节父页面包车型大巴跳转。

三个属性分别能够又简写为 self 与 top,所以当开采大家的页面被嵌套在 iframe 时,能够重定向父级页面:

JavaScript

if (self != top) { // 我们的正规页面 var url = location.href; // 父级页面重定向 top.location = url; }

1
2
3
4
5
6
if (self != top) {
  // 我们的正常页面
  var url = location.href;
  // 父级页面重定向
  top.location = url;
}

 

HTTP劫持

怎么是HTTP要挟呢,大大多场合是营业商HTTP威迫,当我们选用HTTP诉求要求多少个网址页面包车型客车时候,互联网运营商会在健康的数额流中插入用心设计的网络数据报文,让客商端(常常是浏览器)显示“错误”的多寡,平时是部分弹窗,宣传性广告照旧直接显示某网站的原委,大家应该都有相逢过。

运用白名单放行通常 iframe 嵌套

当然比非常多时候,大概运维必要,大家的页面会被以各个方法加大,也是有一点都不小或许是正常工作须要被嵌套在 iframe 中,今年我们须要八个白名单或然黑名单,当我们的页面被嵌套在 iframe 中且父级页面域名存在白名单中,则不做重定向操作。

地点也说了,使用 top.location.href 是不能获得父级页面包车型地铁 U奇骏L 的,那时候,须求运用document.referrer

经过 document.referrer 可以获得跨域 iframe 父页面包车型客车U普拉多L。

JavaScript

// 创建白名单 var whiteList = [ 'www.aaa.com', 'res.bbb.com' ]; if (self != top) { var // 使用 document.referrer 可以得到跨域 iframe 父页面的 UPAJEROL parentUrl = document.referrer, length = whiteList.length, i = 0; for(; i<length; i++){ // 建构白名单正则 var reg = new RegExp(whiteList[i],'i'); // 存在白名单中,放行 if(reg.test(parentUrl)){ return; } } // 大家的符合规律页面 var url = location.href; // 父级页面重定向 top.location = url; }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 建立白名单
var whiteList = [
  'www.aaa.com',
  'res.bbb.com'
];
if (self != top) {
  var
    // 使用 document.referrer 可以拿到跨域 iframe 父页面的 URL
    parentUrl = document.referrer,
    length = whiteList.length,
    i = 0;
  for(; i<length; i++){
    // 建立白名单正则
    var reg = new RegExp(whiteList[i],'i');
    // 存在白名单中,放行
    if(reg.test(parentUrl)){
      return;
    }
  }
  // 我们的正常页面
  var url = location.href;
  // 父级页面重定向
  top.location = url;
}

 

DNS劫持

DNS 威吓正是通过威胁了 DNS 服务器,通过某个手腕得到某域名的剖判记录调控权,进而修改此域名的深入分析结果,导致对该域名的拜访由原IP地址转入到修改后的钦定IP,其结果便是对一定的网站无法访问或访谈的是假网站,进而实现窃取资料依旧破坏原有正平常衣服务的指标。

DNS 威胁比之 HTTP 威迫越发过分,简单说正是大家乞请的是  ,直接被重定向了 ,本文不会过多探讨这种景况。

退换 UEscortL 参数绕过运维商标识

这般就完了吗?没有,我们即便重定向了父页面,不过在重定向的进程中,既然首次能够嵌套,那么这三遍重定向的进程中页面大概又被 iframe 嵌套了,真尼玛蛋疼。

本来运行商这种威逼常常也许有迹可循,最健康的手段是在页面 U昂科雷L 中设置叁个参数,比方  ,其中 iframe_hijack_redirected=1 表示页面已经被威吓过了,就不再嵌套 iframe 了。所以依赖这么些特性,大家得以改写大家的 U揽胜极光L ,使之看上去已经被威吓了:

JavaScript

var flag = 'iframe_hijack_redirected'; // 当前页面存在于贰个 iframe 中 // 此处要求建构贰个白名单相配准则,白名单暗中认可放行 if (self != top) { var // 使用 document.referrer 能够获得跨域 iframe 父页面包车型大巴 ULANDL parentUrl = document.referrer, length = whiteList.length, i = 0; for(; i<length; i++){ // 创建白名单正则 var reg = new RegExp(whiteList[i],'i'); // 存在白名单中,放行 if(reg.test(parentUrl)){ return; } } var url = location.href; var parts = url.split('#'); if (location.search) { parts[0] += '&' + flag + '=1'; } else { parts[0] += '?' + flag + '=1'; } try { console.log('页面被平放iframe中:', url); top.location.href = parts.join('#'); } catch (e) {} }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
var flag = 'iframe_hijack_redirected';
// 当前页面存在于一个 iframe 中
// 此处需要建立一个白名单匹配规则,白名单默认放行
if (self != top) {
  var
    // 使用 document.referrer 可以拿到跨域 iframe 父页面的 URL
    parentUrl = document.referrer,
    length = whiteList.length,
    i = 0;
  for(; i<length; i++){
    // 建立白名单正则
    var reg = new RegExp(whiteList[i],'i');
    // 存在白名单中,放行
    if(reg.test(parentUrl)){
      return;
    }
  }
  var url = location.href;
  var parts = url.split('#');
  if (location.search) {
    parts[0] += '&' + flag + '=1';
  } else {
    parts[0] += '?' + flag + '=1';
  }
  try {
    console.log('页面被嵌入iframe中:', url);
    top.location.href = parts.join('#');
  } catch (e) {}
}

本来,假诺那一个参数一改,防嵌套的代码就失效了。所以大家还索要创设三个陈述系统,当发现页面被嵌套时,发送三个阻拦上报,就算重定向战败,也能够知道页面嵌入 iframe 中的 U奥德赛L,根据分析那个 U途睿欧L ,不断升高我们的防御手腕,这几个后文仲提起。

XSS跨站脚本

XSS指的是攻击者利用漏洞,向 Web 页面中注入恶意代码,当客商浏览该页之时,注入的代码会被施行,进而完结攻击的异样目标。

有关那些攻击怎么样变迁,攻击者怎么样注入恶意代码到页面中本文不做探讨,只要精通如 HTTP 威逼 和 XSS 最后都以恶意代码在顾客端,常常相当于顾客浏览器端试行,本文将商量的就是一旦注入已经存在,怎么样利用 Javascript 进行中用的前端防护。

内联事件及内联脚本拦截

在 XSS 中,其实能够注入脚本的艺术要命的多,尤其是 HTML5 出来之后,一不细心,多数的新标签都能够用来注入可施行脚本。

列出部分比较分布的流入格局:

  1. <a href="javascript:alert(1)" ></a>
  2. <iframe src="javascript:alert(1)" />
  3. <img src='x' onerror="alert(1)" />
  4. <video src='x' onerror="alert(1)" ></video>
  5. <div onclick="alert(1)" onmouseover="alert(2)" ><div>

除了那些之外一些未列出来的非常少见生僻的流入情势,当先一半都以 javascript:... 及内联事件 on*

大家只要注入已经发生,那么有未有一些子拦截这几个内联事件与内联脚本的奉行呢?

对于地点列出的 (1) (5) ,这种必要客商点击或许实践某种事件现在才实践的台本,大家是有一点子开展卫戍的。

 

浏览器事件模型

那边说能够拦截,涉及到了事件模型相关的准则。

笔者们都清楚,标准浏览器事件模型存在四个级次:

  • 抓获阶段
  • 对象阶段
  • 冒泡阶段

对此二个如此 <a href="javascript:alert(222)" ></a> 的 a 标签来说,真正触发成分 alert(222) 是处于点击事件的对象阶段。

See the Pen EyrjkG by Chokcoco (@Chokcoco) on CodePen.

点击上边包车型客车 click me ,先弹出 111 ,后弹出 222。

那么,大家只须求在点击事件模型的抓获阶段对标签内 javascript:... 的原委创建首要字黑名单,实行过滤核查,就足以实现大家想要的遏止效果。

对于 on* 类内联事件也是同理,只是对于那类事件太多,大家不能手动枚举,能够运用代码自动枚举,完结对内联事件及内联脚本的阻止。

以阻挡 a 标签内的 href="javascript:... 为例,大家得以那样写:

JavaScript

// 建构第一词黑名单 var keywordBlackList = [ 'xss', 'BAIDU_SSP__wrapper', 'BAIDU_DSPUI_FLOWBAR' ]; document.add伊芙ntListener('click', function(e) { var code = ""; // 扫描 <a href="javascript:"> 的脚本 if (elem.tagName == 'A' && elem.protocol == 'javascript:') { var code = elem.href.substr(11); if (blackListMatch(keywordBlackList, code)) { // 注销代码 elem.href = 'javascript:void(0)'; console.log('拦截质疑事件:' + code); } } }, true); /** * [黑名单相称] * @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; }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 建立关键词黑名单
var keywordBlackList = [
  'xss',
  'BAIDU_SSP__wrapper',
  'BAIDU_DSPUI_FLOWBAR'
];
  
document.addEventListener('click', function(e) {
  var code = "";
  // 扫描 <a href="javascript:"> 的脚本
  if (elem.tagName == 'A' && elem.protocol == 'javascript:') {
    var code = elem.href.substr(11);
    if (blackListMatch(keywordBlackList, code)) {
      // 注销代码
      elem.href = 'javascript:void(0)';
      console.log('拦截可疑事件:' + code);
    }
  }
}, true);
/**
* [黑名单匹配]
* @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;
}

能够戳作者翻看DEMO。(张开页面后张开调节台查看 console.log)

点击图中那多少个开关,能够看到如下:

奥门威尼斯网址 2

此间大家用到了黑名单相称,下文还有恐怕会细说。

 

页面被放置 iframe 中,重定向 iframe

先来讲说咱俩的页面被安置了 iframe 的意况。也便是,互联网运营商为了尽大概地回降植入广告对原有网站页面包车型地铁熏陶,平时会透过把原来网址页面放置到三个和原页面一样大小的 iframe 里面去,那么就能够通过这几个 iframe 来隔开广告代码对原来页面的熏陶。
奥门威尼斯网址 3

这种气象还相比较好管理,大家只需求明白我们的页面是或不是被嵌套在 iframe 中,假设是,则重定向外层页面到大家的常规页面就可以。

那么有没办法知情大家的页面当前留存于 iframe 中呢?有的,就是 window.self 与 window.top 。

静态脚本拦截

XSS 跨站脚本的精粹不在于“跨站”,在于“脚本”。

一般来说来说,攻击者只怕运转商会向页面中流入贰个<script>本子,具体操作都在剧本中落实,这种威逼方式只供给注入二遍,有转移的话不要求每回都再也注入。

小编们如若未来页面上被注入了七个 <script src="http://attack.com/xss.js"> 脚本,大家的目的便是阻止那几个剧本的施行。

听上去很困难啊,什么意思呢。正是在本子实践前开掘那几个质疑脚本,并且销毁它使之不可能推行内部代码。

于是大家要求选取一些高端 API ,能够在页面加载时对转移的节点开展检查评定。

 

window.self

回到多个针对当前 window 对象的援用。

MutationObserver

MutationObserver 是 HTML5 新增加的 API,成效很有力,给开采者们提供了一种能在有个别范围内的 DOM 树产生变化时作出确切反应的手艺。

说的很微妙,大约的意味正是能够监测到页面 DOM 树的转移,并作出反应。

MutationObserver() 该构造函数用来实例化多个新的Mutation观看者对象。

JavaScript

MutationObserver( function callback );

1
2
3
MutationObserver(
  function callback
);

目瞪狗呆,这一大段又是甚?意思便是 MutationObserver 在察看时毫无发掘一个新因素就随即回调,而是将三个日子某些里出现的有着因素,一齐传过来。所以在回调中大家要求打开批量管理。并且,个中的 callback 会在内定的 DOM 节点(目的节点)发生变化时被调用。在调用时,观察者对象会传给该函数四个参数,第三个参数是个带有了多少个MutationRecord 对象的数组,第贰个参数则是以此观察者对象自己。

故此,使用 MutationObserver ,大家能够对页面加载的各类静态脚本文件,举办督察:

JavaScript

// 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]; if (/xss/i.test(node.src))) { try { node.parentNode.removeChild(node); console.log('拦截困惑静态脚本:', node.src); } catch (e) {} } } }); }); // 传入指标节点和考查选项 // 假若target 为 document 或许 document.documentElement // 则当前文书档案中兼有的节点加多与删除操作都会被观察到 observer.observe(document, { subtree: true, childList: true });

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 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];
      if (/xss/i.test(node.src))) {
        try {
          node.parentNode.removeChild(node);
          console.log('拦截可疑静态脚本:', node.src);
        } catch (e) {}
      }
    }
  });
});
// 传入目标节点和观察选项
// 如果 target 为 document 或者 document.documentElement
// 则当前文档中所有的节点添加与删除操作都会被观察到
observer.observe(document, {
  subtree: true,
  childList: true
});

能够看来如下:可以戳作者查看DEMO。(打开页面后展开调整台查看 console.log)

奥门威尼斯网址 4

<script type="text/javascript" src="./xss/a.js"></script> 是页面加载一方始就存在的静态脚本(查看页面结构),大家利用 MutationObserver 能够在本子加载之后,试行此前那一个小时段对其内容做正则相称,开掘恶意代码则 removeChild()奥门威尼斯网址 , 掉,使之不能够施行。

window.top

归来窗口种类中的最顶层窗口的援引。

对于非同源的域名,iframe 子页面无法透过 parent.location 可能 top.location 获得实际的页面地址,然而能够写入 top.location ,也正是能够调整父页面包车型客车跳转。

七个属性分别可以又简写为 self 与 top,所以当发掘我们的页面被嵌套在 iframe 时,能够重定向父级页面:

if (self != top) {
  // 我们的正常页面
  var url = location.href;
  // 父级页面重定向
  top.location = url;
}

  

应用白名单对 src 举行相配过滤

上边的代码中,大家看清叁个js脚本是或不是是恶意的,用的是这一句:

JavaScript

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

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

自然实际当中,注入恶意代码者不会那么傻,把名字改成 XSS 。所以,大家很有至关重要接纳白名单进行过滤和建立四个阻拦上报系统。

JavaScript

// 创建白名单 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); }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 建立白名单
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);
}

此处我们早就数13遍关联白名单相配了,下文还也许会用到,所以可以这里把它回顾封装成多少个方法调用。

选择白名单放行平常 iframe 嵌套

理之当然比较多时候,或者运维须求,大家的页面会被以种种措施加大,也可以有比非常的大可能是平常专业要求被嵌套在 iframe 中,这年咱们需求三个白名单或许黑名单,当大家的页面被嵌套在 iframe 中且父级页面域名存在白名单中,则不做重定向操作。

地点也说了,使用 top.location.href 是不能获得父级页面的 URubiconL 的,那时候,须要利用document.referrer

经过 document.referrer 可以得到跨域 iframe 父页面包车型地铁U福特ExplorerL。

// 建立白名单
var whiteList = [
  'www.aaa.com',
  'res.bbb.com'
];

if (self != top) {
  var
    // 使用 document.referrer 可以拿到跨域 iframe 父页面的 URL
    parentUrl = document.referrer,
    length = whiteList.length,
    i = 0;

  for(; i<length; i++){
    // 建立白名单正则
    var reg = new RegExp(whiteList[i],'i');

    // 存在白名单中,放行
    if(reg.test(parentUrl)){
      return;
    }
  }

  // 我们的正常页面
  var url = location.href;
  // 父级页面重定向
  top.location = url;
}

 

动态脚本拦截

下面使用 MutationObserver 拦截静态脚本,除了静态脚本,与之对应的正是动态变化的剧本。

JavaScript

var script = document.createElement('script'); script.type = 'text/javascript'; script.src = ''; document.getElementsByTagName('body')[0].appendChild(script);

1
2
3
4
5
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 事件的。

退换 UTucsonL 参数绕过运维商标识

如此那般就完了吗?未有,大家固然重定向了父页面,不过在重定向的经过中,既然第4回能够嵌套,那么那三回重定向的历程中页面恐怕又被 iframe 嵌套了,真尼玛蛋疼。

本来运营商这种威迫平日也可以有迹可循,最健康的手腕是在页面 U景逸SUVL 中安装一个参数,举个例子  ,其中 iframe_hijack_redirected=1 表示页面已经被吓唬过了,就不再嵌套 iframe 了。所以依赖这么些特点,大家能够改写我们的 U福睿斯L ,使之看上去已经被威胁了:

var flag = 'iframe_hijack_redirected';
// 当前页面存在于一个 iframe 中
// 此处需要建立一个白名单匹配规则,白名单默认放行
if (self != top) {
  var
    // 使用 document.referrer 可以拿到跨域 iframe 父页面的 URL
    parentUrl = document.referrer,
    length = whiteList.length,
    i = 0;

  for(; i<length; i++){
    // 建立白名单正则
    var reg = new RegExp(whiteList[i],'i');

    // 存在白名单中,放行
    if(reg.test(parentUrl)){
      return;
    }
  }

  var url = location.href;
  var parts = url.split('#');
  if (location.search) {
    parts[0] += '&' + flag + '=1';
  } else {
    parts[0] += '?' + flag + '=1';
  }
  try {
    console.log('页面被嵌入iframe中:', url);
    top.location.href = parts.join('#');
  } catch (e) {}
}

当然,假若那么些参数一改,防嵌套的代码就失效了。所以大家还索要树立二个上报系统,当开掘页面被嵌套时,发送二个拦住上报,尽管重定向败北,也得以领略页面嵌入 iframe 中的 URAV4L,依照深入分析那几个 U途观L ,不断拉长大家的严防花招,那一个后文种聊到。

 

Mutation Events 与 DOMNodeInserted

打开 MDN ,第一句正是:

该天性已经从 Web 标准中删去,固然有个别浏览器方今照例支撑它,但恐怕会在以后的某部时刻甘休协助,请尽可能不要选用该本性。

固然不能用,也得以明白一下:

JavaScript

document.add伊夫ntListener('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);

1
2
3
4
5
6
7
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 相比较,它的施行机遇更早。

奥门威尼斯网址 5

但是 DOMNodeInserted 不再提出使用,所以监听动态脚本的职务也要付出 MutationObserver

惋惜的是,在实际上实践进度中,使用 MutationObserver 的结果和 DOMNodeInserted 同样,能够监听拦截到动态脚本的成形,可是不能在剧本实行从前,使用 removeChild 将其移除,所以大家还需求思量别的方法。

内联事件及内联脚本拦截

在 XSS 中,其实能够注入脚本的方法相当的多,尤其是 HTML5 出来之后,一不留意,多数的新标签都足以用于注入可实行脚本。

列出一部分相比普及的流入情势:

  1. <a href="javascript:alert(1)" ></a>
  2. <iframe src="javascript:alert(1)" />
  3. <img src='x' onerror="alert(1)" />
  4. <video src='x' onerror="alert(1)" ></video>
  5. <div onclick="alert(1)" onmouseover="alert(2)" ><div>

而外一些未列出来的不胜少见生僻的流入情势,抢先百分之五十都是 javascript:... 及内联事件 on*

咱俩只要注入已经发生,那么有未有措施拦截那几个内联事件与内联脚本的推行呢?

对此地方列出的 (1) (5) ,这种须要客商点击或然施行某种事件过后才实施的本子,大家是有措施开展防卫的。

重写 setAttribute 与 document.write

浏览器事件模型

此处说能够阻挡,涉及到了事件模型连带的规律。

大家都精晓,标准浏览器事件模型存在七个等第:

  • 抓获阶段
  • 指标阶段
  • 冒泡阶段

对于三个这么 <a href="javascript:alert(222)" ></a> 的 a 标签来说,真正触发成分 alert(222) 是处于点击事件的靶子阶段。

点击下面的 click me ,先弹出 111 ,后弹出 222。

那么,我们只须求在点击事件模型的破获阶段对标签内 javascript:... 的剧情构建珍视字黑名单,举行过滤检查核对,就足以成功我们想要的阻挠效果。

对于 on* 类内联事件也是同理,只是对于这类事件太多,大家无法手动枚举,能够采纳代码自动枚举,完结对内联事件及内联脚本的阻拦。

以堵住 a 标签内的 href="javascript:... 为例,大家得以如此写:

// 建立关键词黑名单
var keywordBlackList = [
  'xss',
  'BAIDU_SSP__wrapper',
  'BAIDU_DSPUI_FLOWBAR'
];

document.addEventListener('click', function(e) {
  var code = "";

  // 扫描 <a href="javascript:"> 的脚本
  if (elem.tagName == 'A' && elem.protocol == 'javascript:') {
    var code = elem.href.substr(11);

    if (blackListMatch(keywordBlackList, code)) {
      // 注销代码
      elem.href = 'javascript:void(0)';
      console.log('拦截可疑事件:' + code);
    }
  }
}, true);

/**
 * [黑名单匹配]
 * @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;
}

可以戳小编翻看DEMO。(展开页面后展开调节台查看 console.log) 

点击图中那多少个开关,能够见见如下:

奥门威尼斯网址 6

这边大家用到了黑名单相称,下文还有或者会细说。

 

重写原生 Element.prototype.setAttribute 方法

在动态脚本插入实践前,监听 DOM 树的转换拦截它不行,脚本照旧会试行。

那正是说大家须求向上研究,在剧本插入 DOM 树前的捕获它,这正是创办脚本时那个时机。

倘若今后有三个动态脚本是那般创制的:

JavaScript

var script = document.createElement('script'); script.setAttribute('type', 'text/javascript'); script.setAttribute('src', ''); document.getElementsByTagName('body')[0].appendChild(script);

1
2
3
4
5
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 属性时的值,通过黑名单也许白名单决断它,就能够确定该标签的合法性了。

JavaScript

// 保存原有接口 var old_setAttribute = Element.prototype.setAttribute; // 重写 setAttribute 接口 Element.prototype.setAttribute = function(name, value) { // 相配到 <script src='xxx' > 类型 if (this.tagName == 'SC酷威IPT' && /^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; }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// 保存原有接口
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)

奥门威尼斯网址 7

重写 Element.prototype.setAttribute ,正是率先保存原有接口,然后当有成分调用 setAttribute 时,检查传入的 src 是或不是存在于白名单中,存在则放行,不设有则就是困惑成分,实行反映并不授予施行。最终对放行的成分试行原生的 setAttribute ,也就是 old_setAttribute.apply(this, arguments);

上述的白名单相配也能够换来黑名单相称。

静态脚本拦截

XSS 跨站脚本的优异不在于“跨站”,在于“脚本”。

日常来说,攻击者大概运转商会向页面中注入贰个<script>本子,具体操作都在本子中完成,这种威胁方式只须求注入一遍,有改动的话没有供给每一遍都重复注入。

小编们假若现在页面上被注入了一个 <script src="http://attack.com/xss.js"> 脚本,大家的靶子正是拦住这么些剧本的推行。

听上去很不方便啊,什么意思吧。正是在剧本试行前发掘那些质疑脚本,並且销毁它使之不能够实行内部代码。

于是大家要求运用一些高档API ,能够在页面加载时对转移的节点开展检查评定。

 

MutationObserver

MutationObserver 是 HTML5 新扩充的 API,作用很强大,给开垦者们提供了一种能在有些范围内的 DOM 树发生变化时作出确切反应的力量。

说的很神秘,差不离的意趣正是能力所能达到监测到页面 DOM 树的改换,并作出反应。

MutationObserver() 该构造函数用来实例化八个新的Mutation观察者对象。

MutationObserver(
  function callback
);

目瞪狗呆,这一大段又是什么?意思正是MutationObserver 在观看时不用开采一个新因素就登时回调,而是将二个小时部分里涌出的全数因素,一齐传过来。所以在回调中大家须求进行批量甩卖。并且,当中的 callback 会在钦点的 DOM 节点(目的节点)发生变化时被调用。在调用时,观望者对象会传给该函数七个参数,第三个参数是个富含了若干个 MutationRecord 对象的数组,第一个参数则是其一观望者对象自作者。

因而,使用 MutationObserver ,大家得以对页面加载的种种静态脚本文件,进行监督:

// 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];
      if (/xss/i.test(node.src))) {
        try {
          node.parentNode.removeChild(node);
          console.log('拦截可疑静态脚本:', node.src);
        } catch (e) {}
      }
    }
  });
});

// 传入目标节点和观察选项
// 如果 target 为 document 或者 document.documentElement
// 则当前文档中所有的节点添加与删除操作都会被观察到
observer.observe(document, {
  subtree: true,
  childList: true
});

能够看来如下:能够戳小编翻看DEMO。(打开页面后张开调整台查看 console.log)

奥门威尼斯网址 8

<script type="text/javascript" src="./xss/a.js"></script> 是页面加载一发端就存在的静态脚本(查看页面结构),大家使用 MutationObserver 能够在本子加载之后,实行此前那几个时间段对其内容做正则相配,发掘恶意代码则 removeChild() 掉,使之无法试行。

重写嵌套 iframe 内的 Element.prototype.setAttribute

理当如此,上边的写法如若 old_setAttribute = Element.prototype.setAttribute 揭露给攻击者的话,直接使用old_setAttribute 就足以绕过大家重写的措施了,所以这段代码必需包在贰个闭包内。

当然如此也不有限扶助,就算日前窗口下的 Element.prototype.setAttribute 已经被重写了。不过照旧有一手可以获得原生的 Element.prototype.setAttribute ,只须要二个新的 iframe 。

JavaScript

var newIframe = document.createElement('iframe'); document.body.appendChild(newIframe); Element.prototype.setAttribute = newIframe.contentWindow.Element.prototype.setAttribute;

1
2
3
4
var newIframe = document.createElement('iframe');
document.body.appendChild(newIframe);
Element.prototype.setAttribute = newIframe.contentWindow.Element.prototype.setAttribute;

经过那个办法,能够重复得到原生的 Element.prototype.setAttribute ,因为 iframe 内的条件和外围 window 是全然隔开分离的。wtf?

奥门威尼斯网址 9

如何做?大家看来创制 iframe 用到了 createElement,那么是不是足以重写原生 createElement 呢?可是除外createElement 还有 createElementNS ,还应该有相当大希望是页面上业已存在 iframe,所以不正好。

那就在每当新创造一个新 iframe 时,对 setAttribute 举行敬爱重写,这里又有用到 MutationObserver :

JavaScript

/** * 使用 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) { ... }; }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/**
* 使用 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 方法,以此进行层层珍重。

 

本文由威尼斯国际官方网站发布于奥门威尼斯网址,转载请注明出处:前端安全

关键词: