9159金沙游艺场


str字符串 encoding( ) 方法
图片 1
[PE结构分析] 5.IMAGE_OPTIONAL_HEADER

【前端安全】JavaScript防http劫持与XSS

打造双剑合璧的 XSS 前端防火墙

2015/09/30 · HTML5 ·
XSS

原文出处: 林子杰(@Zack__lin)   

作为前端,一直以来都知道HTTP劫持XSS跨站脚本(Cross-site
scripting)、CSRF跨站请求伪造(Cross-site request
forgery)。但是一直都没有深入研究过,前些日子同事的分享会偶然提及,我也对这一块很感兴趣,便深入研究了一番。

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 ,本文示例代码,防范方法在组件源码中皆可找到。

接下来进入正文。

前言

深入接触 xss 注入是从排查业务的广告注入开始,以前对 xss
注入片面认为是页面输入的安全校验漏洞导致一系列的问题,通过对 zjcqoo
的《XSS 前端防火墙》系列文章,认识到自己其实对 XSS
注入的认识还真是半桶水。

最近用
JavaScript 写了一个组件,可以在前端层面防御部分 HTTP 劫持与 XSS。

HTTP劫持、DNS劫持与XSS

先简单讲讲什么是 HTTP 劫持与 DNS 劫持。

捣蛋的运营商

由于 xss 注入的范围太广,本文仅对网关劫持这一方面的 XSS 注入进行讨论。
这里读者有个小小的疑问,为什么我要选网关劫持进行讨论?因为网关劫持可以大面积范围进行有效控制。

曾经,有这样一道风靡前端的面试题(当然我也现场笔试过):当你在浏览器地址栏输入一个URL后回车,将会发生的事情?其实本文不关心请求发到服务端的具体过程,但是我关心的时,服务端响应输出的文档,可能会在哪些环节被注入广告?手机、路由器网关、网络代理,还有一级运营商网关等等。所以,无论如何,任何网页都得经过运营商网关,而且最调(zui)皮(da)捣(e)蛋(ji)的,就是通过运营商网关。

另外,
也提醒大家,如果手机安装了一些上网加速软件、网络代理软件或设置网络代理
IP,会有安全风险,也包括公共场所/商家的免费 WIFI。

当然,防御这些劫持最好的方法还是从后端入手,前端能做的实在太少。而且由于源码的暴露,攻击者很容易绕过我们的防御手段。但是这不代表我们去了解这块的相关知识是没意义的,本文的许多方法,用在其他方面也是大有作用。

HTTP劫持

什么是HTTP劫持呢,大多数情况是运营商HTTP劫持,当我们使用HTTP请求请求一个网站页面的时候,网络运营商会在正常的数据流中插入精心设计的网络数据报文,让客户端(通常是浏览器)展示“错误”的数据,通常是一些弹窗,宣传性广告或者直接显示某网站的内容,大家应该都有遇到过。

前端防火墙的实践

经过近一段时间通过对 zjcqoo 的《XSS
前端防火墙》六板斧的反复琢磨理解,基本上防御措施可以归为两大类:一种是从协议上屏蔽,一种是从前端代码层面进行拦截移除。通过
zjcqoo
提出的几种注入防御方式,进行几个月的实践观察,对广告注入方式大概可以归为两种:完全静态注入、先静态注入后动态修改(创建)。

  1. 完全静态注入
    完全内联 js、css、和 dom,不管是 body
    内外,甚是恶心,而且如果是在监控脚本前面注入的,还可以抢先执行,造成防御不起作用。注入的
    DOM 也无法清除。
  2. 先静态注入后动态修改
    这种可以分为几种:一种是异步请求接口数据再生成 DOM 注入,一种是修改
    iframe 源地址进行引入,另外一种是修改 script 源地址,请求执行 js
    再异步获取数据或生成 DOM。

已上传到
Github
– httphijack.js ,欢迎感兴趣看看顺手点个
star ,本文示例代码,防范方法在组件源码中皆可找到。

DNS劫持

DNS劫持就是通过劫持了DNS服务器,通过某些手段取得某域名的解析记录控制权,进而修改此域名的解析结果,导致对该域名的访问由原IP地址转入到修改后的指定IP,其结果就是对特定的网址不能访问或访问的是假网址,从而实现窃取资料或者破坏原有正常服务的目的。

DNS 劫持就更过分了,简单说就是我们请求的是 
,直接被重定向了
,本文不会过多讨论这种情况。

监控数据观察分析

对 zjcqoo
提出的几种防御方式的实践,前一个月主要是花在优化检测脚本和增加白名单过滤脏数据方面,因为这块事情只能利用业余时间来搞,所以拖的时间有点久。白名单这块的确是比较繁琐,很多人以为分析下已知的域名就
ok 了,其实不然,云龙在这篇 iframe
黑魔法就提到移动端 Native 与 web
的通信机制,所以在各种 APP 上,会有各种 iframe
的注入,而且是各种五花八门的协议地址,也包括 chrome。

监控拿到的数据很多,但是,由于对整个广告注入黑产行业的不熟悉,所以,有必要借助
google
进行查找研究,发现,运营商大大地狡猾,他们自己只会注入自己业务的广告,如
4G
免费换卡/送流量/送话费,但是商业广告这块蛋糕他们会拱手让人?答案是不可能,他们会勾结其他广告代理公司,利用他们的广告分发平台(运营商被美名为广告系统平台提供商)进行广告投放然后分成…

对于用户投诉,他们一般都是认错,然后对这个用户加白名单,但是他们对其他用户还是继续作恶。对于企业方面的投诉,如果影响到他们的域名,如果你没有确凿的证据,他们就会用各种借口摆脱自己的责任,如用户手机中毒等等,如果你有确凿的证据,还得是他们运营商自己的域名或者
IP,否则他们也无法处理。他们还是一样的借口,用户手机中毒等等。

除非你把运营商的域名或 IP
监控数据列给他看,他才转变态度认错,但是这仅仅也是之前我们提到的流量话费广告,对于第三方广告代理商的广告,还是没法解决,这些第三方广告代理商有广告家、花生米、XX
传媒等等中小型广告商,当然也不排除,有的是“个体户广告商”。

从另一方面来看,由于使用的是古老的 http 协议,这种明文传输的协议,html
内容可以被运营商一清二楚地记录下来,页面关键字、访问时间、地域等用户标签都可以进行采集,说到这,你可能已经明白了一个事(隐私侵犯已经见怪不怪了)——大数据分析+个性化推荐,在
google 一查,运营商还真有部署类似于 iPush
网络广告定向直投这样的系统,而且广告点击率也出奇的高,不排除会定向推送一些偏黄色的图片或游戏。

另外,数据分析中发现一些百度统计的接口请求,也在一些 js
样本中发现百度统计地址,猜测很有可能是这种广告平台利用百度统计系统做数据分析,如定向投放用户
PV 统计,广告效果统计等等。
监控数据分析也扯这么多了,我们还是回来看怎么做防御措施吧!

接下来进入正文。

XSS跨站脚本

XSS指的是攻击者漏洞,向 Web
页面中注入恶意代码,当用户浏览该页之时,注入的代码会被执行,从而达到攻击的特殊目的。

关于这些攻击如何生成,攻击者如何注入恶意代码到页面中本文不做讨论,只要知道如
HTTP 劫持 和 XSS
最终都是恶意代码在客户端,通常也就是用户浏览器端执行,本文将讨论的就是假设注入已经存在,如何利用
Javascript 进行行之有效的前端防护。

防御措施介绍

 

页面被嵌入 iframe 中,重定向 iframe

先来说说我们的页面被嵌入了 iframe
的情况。也就是,网络运营商为了尽可能地减少植入广告对原有网站页面的影响,通常会通过把原有网站页面放置到一个和原页面相同大小的
iframe 里面去,那么就可以通过这个 iframe
来隔离广告代码对原有页面的影响。
图片 1

这种情况还比较好处理,我们只需要知道我们的页面是否被嵌套在 iframe
中,如果是,则重定向外层页面到我们的正常页面即可。

那么有没有方法知道我们的页面当前存在于 iframe
中呢?有的,就是 window.self 与 window.top 。

全站 HTTPS + HSTS

开启 HTTPS,可以加强数据保密性、完整性、和身份校验,而 HSTS (全称 HTTP
Strict Transport Security)可以保证浏览器在很长时间里都会只用 HTTPS
访问站点,这是该防御方式的优点。但是,缺点和缺陷也不可忽略。

互联网全站HTTPS的时代已经到来 一文已有详细的分析,加密解密的性能损耗在服务端的损耗和网络交互的损耗,但是移动端浏览器和
webview 的兼容性支持却是个问题,比如 Android webview
需要固件4.4以上才支持,iOS safari 8 以上也才支持,而 UC
浏览器目前还不支持。

而目前推动团队所有业务支持 HTTPS 难度也是相当高,部分 302
重定向也有可能存在 SSLStrip,更何况 UC
浏览器还不支持这个协议,很容易通过 SSLStrip
进行劫持利用,虽然运营商大部分情况下不会这么干,但是我还是坚定怀疑他们的节操。由于我国宽带网络的基本国情,短时间指望速度提升基本上不可能的,就算总理一句话,但哪个运营商不想赚钱?所以,业务性能的下降和业务安全,需要进行权衡利弊。

HTTP劫持、DNS劫持与XSS

先简单讲讲什么是
HTTP 劫持与 DNS 劫持。

window.self

返回一个指向当前 window 对象的引用。

Content Security Policy(简称 CSP)

CSP
内容安全策略,属于一种浏览器安全策略,以可信白名单作机制,来限制网站中是否可以包含某来源内容。兼容性支持同样是个问题,比如
Android webview 需要固件4.4以上才支持,iOS safari 6 以上支持,幸运的是
UC 浏览器目前支持 1.0
策略版本,具体可以到 CANIUSE 了解。目前对
CSP 的使用仅有不到两周的经验而已,下面简单说说其优缺点。

缺点:

  1. CSP
    规范也比较累赘,每种类型需要重新配置一份,默认配置不能继承,只能替换,这样会导致整个
    header 内容会大大增加。
  2. 如果业务中有爬虫是抓取了外部图片的话,那么 img
    配置要么需要枚举各种域名,要么就信任所有域名。
    1. 移动端 web app 页面,如果有存在 Native 与 web 的通信,那么 iframe
      配置只能信任所有域名和协议了。
    1. 一些业务场景导致无法排除内联 script 的情况,所以只能开启
      unsafe-inline
    1. 一些库仍在使用 eval,所以避免误伤,也只能开启 unsafe-eval
    1. 由于 iframe 信任所有域名和协议,而 unsafe-inline
      开启,使得整个防御效果大大降低

优点:

  1. 通过 connect/script 配置,我们可以控制哪些
    外部域名异步请求可以发出,这无疑是大大的福音,即使内联 script
    被注入,异步请求仍然发不出,这样一来,除非攻击者把所有的 js
    都内联进来,否则注入的功能也运行不了,也无法统计效果如何。
  2. 通过 reportUri 可以统计到攻击类型和
    PV,只不过这个接口的设计不能自定义,上报的内容大部分都是鸡肋。
  3. object/media
    配置可以屏蔽一些外部多媒体的加载,不过这对于视频播放类的业务,也会误伤到。
  4. 目前 UC 浏览器 Android 版本的客户端和 web 端通信机制都是采用标准的
    addJavascriptInterface 注入方式,而 iPhone 版本已将 iframe
    通信方式改成 ajax 方式(与页面同域,10.5
    全部改造完成),如果是只依赖 UC
    浏览器的业务,可以大胆放心使用,如果是需要依赖于第三方平台,建议先开启
    reportOnly,将一些本地协议加入白名单,再完全开启防御。

总的来说吧,单靠 CSP
单打独斗显然是不行,即使完全开启所有策略,也不能完成消除注入攻击,但是作为纵深防御体系中的一道封锁防线,价值也是相当有用的。

HTTP劫持

什么是HTTP劫持呢,大多数情况是运营商HTTP劫持,当我们使用HTTP请求请求一个网站页面的时候,网络运营商会在正常的数据流中插入精心设计的网络数据报文,让客户端(通常是浏览器)展示“错误”的数据,通常是一些弹窗,宣传性广告或者直接显示某网站的内容,大家应该都有遇到过。

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;
}

 

前端防火墙拦截

前端防火墙显然适合作为第一道防线进行设计,可以预先对一些注入的内联 js
代码、script/iframe 源引用进行移除,同时对 script/iframe
源地址修改做监控移除。
基本设计逻辑大概如下:

图片 2

详细的实现逻辑,参考zjcqoo 的《XSS 前端防火墙》系列文章。

缺点:

  1. 如果是在监控脚本执行前,注入的脚本已经执行,显然后知后觉无法起防御作用了。
  2. 一些 DOM 的注入显然无能为力。

优点:

  1. 可以针对 iframe 做一些自定义的过滤规则,防止对本地通信误伤。
  2. 可以收集到一些注入行为数据进行分析。

DNS劫持

DNS
劫持就是通过劫持了 DNS
服务器,通过某些手段取得某域名的解析记录控制权,进而修改此域名的解析结果,导致对该域名的访问由原IP地址转入到修改后的指定IP,其结果就是对特定的网址不能访问或访问的是假网址,从而实现窃取资料或者破坏原有正常服务的目的。

DNS
劫持比之 HTTP 劫持
更加过分,简单说就是我们请求的是 
,直接被重定向了
,本文不会过多讨论这种情况。

使用白名单放行正常 iframe 嵌套

当然很多时候,也许运营需要,我们的页面会被以各种方式推广,也有可能是正常业务需要被嵌套在
iframe 中,这个时候我们需要一个白名单或者黑名单,当我们的页面被嵌套在
iframe 中且父级页面域名存在白名单中,则不做重定向操作。

上面也说了,使用 top.location.href 是没办法拿到父级页面的 URL
的,这时候,需要使用document.referrer

通过 document.referrer 可以拿到跨域 iframe 父页面的URL。

JavaScript

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

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;
}

 

双剑合璧

即使是单纯的 DOM
注入,显然无法满足更高级功能的使用,也会使运营商的广告分发平台效果大打折扣。如果单独其中一种方式进行使用,也只是发挥了一招一式的半成功力,如果是双手互搏,那也可以发挥成倍的功力。

而前端防火墙再加上 CSP
安全策略,双剑合璧,则可以大大降低广告注入带来的负面效果,重则造成广告代码严重瘫痪无法运行:在监控脚本后注入广告脚本,基本上可以被前端防火墙封杀殆尽,即使有漏网之鱼,也会被
CSP 进行追杀,不死也残。

即使在监控脚本运行前注入,通过 CSP content-src
策略,可以拦截白名单域名列表外的接口请求,使得广告代码的异步请求能力被封杀,script-src
策略,也可以封杀脚本外链的一些外部请求,进一步封杀异步脚本引用,frame-src
策略无论先后创建的 iframe,一律照杀。

侥幸者躲过了初一,却躲不过十五,前端防火墙拍马赶到,照样封杀无误,唯一的路径只有注入
DOM 这一方式,别忘了,只要开启 img-src
策略配置,广告代码只剩下文字链。虽然是一个文字链广告,但点击率又能高到哪去呢?

如果你是 node
派系,小弟附上《辟邪剑谱》 helmet 一本,如果你的业务有涉及到
UCBrowser,更有《辟邪剑谱之 UC
版》helmet-csp-uc 。

所谓道高一尺魔高一丈,既然我们有高效的防御措施,相信他们不久也会探索出反防御方式,如此,我们也需要和这帮人斗智斗勇,一直等到
HTTP/2 规范的正式落地。

1 赞 3 收藏
评论

图片 3

XSS跨站脚本

XSS指的是攻击者利用漏洞,向
Web
页面中注入恶意代码,当用户浏览该页之时,注入的代码会被执行,从而达到攻击的特殊目的。

关于这些攻击如何生成,攻击者如何注入恶意代码到页面中本文不做讨论,只要知道如
HTTP 劫持 和 XSS
最终都是恶意代码在客户端,通常也就是用户浏览器端执行,本文将讨论的就是假设注入已经存在,如何利用
Javascript 进行行之有效的前端防护。

更改 URL 参数绕过运营商标记

这样就完了吗?没有,我们虽然重定向了父页面,但是在重定向的过程中,既然第一次可以嵌套,那么这一次重定向的过程中页面也许又被
iframe 嵌套了,真尼玛蛋疼。

当然运营商这种劫持通常也是有迹可循,最常规的手段是在页面 URL
中设置一个参数,例如
 ,其中 iframe_hijack_redirected=1 表示页面已经被劫持过了,就不再嵌套
iframe 了。所以根据这个特性,我们可以改写我们的 URL
,使之看上去已经被劫持了:

JavaScript

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) {} }

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 中的 URL,根据分析这些 URL
,不断增强我们的防护手段,这个后文会提及。

 

内联事件及内联脚本拦截

在 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)
,这种需要用户点击或者执行某种事件之后才执行的脚本,我们是有办法进行防御的。

页面被嵌入 iframe 中,重定向 iframe

先来说说我们的页面被嵌入了
iframe
的情况。也就是,网络运营商为了尽可能地减少植入广告对原有网站页面的影响,通常会通过把原有网站页面放置到一个和原页面相同大小的
iframe 里面去,那么就可以通过这个 iframe
来隔离广告代码对原有页面的影响。
图片 4

这种情况还比较好处理,我们只需要知道我们的页面是否被嵌套在
iframe 中,如果是,则重定向外层页面到我们的正常页面即可。

那么有没有方法知道我们的页面当前存在于
iframe 中呢?有的,就是 window.self 与 window.top 。

浏览器事件模型

这里说能够拦截,涉及到了事件模型相关的原理。

我们都知道,标准浏览器事件模型存在三个阶段:

  • 捕获阶段
  • 目标阶段
  • 冒泡阶段

对于一个这样 <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.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; }

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)

点击图中这几个按钮,可以看到如下:

图片 5

这里我们用到了黑名单匹配,下文还会细说。

 

window.self

返回一个指向当前
window 对象的引用。

静态脚本拦截

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

通常而言,攻击者或者运营商会向页面中注入一个<script>脚本,具体操作都在脚本中实现,这种劫持方式只需要注入一次,有改动的话不需要每次都重新注入。

我们假定现在页面上被注入了一个 <script src="http://attack.com/xss.js"> 脚本,我们的目标就是拦截这个脚本的执行。

听起来很困难啊,什么意思呢。就是在脚本执行前发现这个可疑脚本,并且销毁它使之不能执行内部代码。

所以我们需要用到一些高级 API ,能够在页面加载时对生成的节点进行检测。

 

window.top

返回窗口体系中的最顶层窗口的引用。

对于非同源的域名,iframe
子页面无法通过 parent.location 或者 top.location
拿到具体的页面地址,但是可以写入 top.location
,也就是可以控制父页面的跳转。

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

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

  

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)

图片 6

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

使用白名单放行正常 iframe 嵌套

当然很多时候,也许运营需要,我们的页面会被以各种方式推广,也有可能是正常业务需要被嵌套在
iframe 中,这个时候我们需要一个白名单或者黑名单,当我们的页面被嵌套在
iframe 中且父级页面域名存在白名单中,则不做重定向操作。

上面也说了,使用
top.location.href 是没办法拿到父级页面的 URL
的,这时候,需要使用document.referrer

通过
document.referrer 可以拿到跨域 iframe 父页面的URL。

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

 

使用白名单对 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);
}

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

相关文章

No Comments, Be The First!
近期评论
    功能
    网站地图xml地图