XSS 前端防火墙(2):可疑模块拦截
由于是在前端防护,策略配置都能在源代码里找到,因此很快就能试出破解方案。并且攻击者可以屏蔽日志接口,在自己电脑上永不发出报警信息,保证测试时不会被发现。
昨天提到最简单并且最常见的 XSS 代码,就是加载站外的一个脚本文件。对于这种情况,关键字扫描就无能为力了,因为代码可以混淆的千变万化,我们看不出任何异常,只能将其放行。
因此,我们还需增加一套可疑模块跟踪系统。
被动扫描
和之前说的一样,最简单的办法仍是遍历扫描。我们可以定时分析页面里的脚本元素,发现有站外地址的脚本就发送预警日志。
如果昨天说的内联事件使用定时扫描,或许还能在触发前拦截一部分,但对于脚本则完全不可能了。脚本元素一旦被挂载到主节点之下,就立即加载并执行了。除非定时器开的特别短,能在脚本加载的过程中将其销毁,或许还能拦截,否则一不留神就错过了。
我们得寻找更高端的浏览器接口,能在元素创建或添加时,进行分析和拦截。
主动防御
在无所不能的 HTML5 里,这当然是能办到的,它就是 MutationEvent。与其相关的有两个玩意:一个叫 DOMNodeInserted 的事件,另一个则是 MutationObserver 类。
前者虽然是个事件,但即使阻止冒泡它,或调用 preventDefault 这些方法,仍然无法阻止元素被添加;而后者就不用说了,看名字就是一个观察器,显然优先级会更低。
MutationEvent 试探
但不管能否实现我们的目标,既然有这么个东西,就先测试看看究竟能有多大的本领。
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
|
<script> var observer = new MutationObserver( function (mutations) { console.log( 'MutationObserver:' , mutations); }); observer.observe(document, { subtree: true , childList: true }); document.addEventListener( 'DOMNodeInserted' , function (e) { console.log( 'DOMNodeInserted:' , e); }, true ); </script> <script>console.warn( 'site-in xss 1' );</script> <script src= "http://www.etherdream.com/xss/out.js" ></script> <script>console.warn( 'site-in xss 2' );</script> <button id= "btn" >创建脚本</button> <script> btn.onclick = function () { var el = document.createElement( 'script' ); el.src = 'http://www.etherdream.com/xss/out.js?dynamic' ; document.body.appendChild(el); }; </script> |
出乎意料的是,MutationObserver 居然能逐一捕捉到页面加载时产生的静态元素,这在过去只能通过定时器才能勉强实现。同时为了更高效的记录,MutationObserver 并非发现新元素就立即回调,而是将一个时间片段里出现的所有元素,一起传过来。这对性能来说是件好事,但显然会损失一些优先级。
再看 DOMNodeInserted,它虽然无法捕获到静态元素,但在动态创建元素时,它比 MutationObserver 更早触发,拥有更高的优先级。
静态脚本拦截
接着再来尝试,能否利用这两个事件,销毁可疑的脚本元素,以达到主动拦截的效果。
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
|
<script> var observer = new MutationObserver( function (mutations) { mutations.forEach( function (mutation) { var nodes = mutation.addedNodes; for ( var i = 0; i < nodes.length; i++) { var node = nodes[i]; if (/xss/.test(node.src) || /xss/.test(node.innerHTML)) { node.parentNode.removeChild(node); console.log( '拦截可疑模块:' , node); } } }); }); observer.observe(document, { subtree: true , childList: true }); </script> <script>console.warn( 'site-in xss 1' );</script> <script src= "http://www.etherdream.com/xss/out.js" ></script> <script>console.warn( 'site-in xss 2' );</script> <button id= "btn" >创建脚本</button> <script> btn.onclick = function () { var el = document.createElement( 'script' ); el.src = 'http://www.etherdream.com/x\ss/out.js?dynamic' ; document.body.appendChild(el); }; </script> |
又是一个出人意料的结果,所有静态脚本被成功拦截了!
然而这并非标准。FireFox 虽然拦截到脚本,但仍然执行代码了。
不过对于预警系统来说,能够发现问题也足够了,可以拦截风险那就再好不过。
动态脚本拦截
刚刚测试了静态脚本的拦截,取得了不错的成绩。但在动态创建的元素上,和我们先前猜测的一样,MutationObserver 因优先级过低而无法拦截。
那就让 DOMNodeInserted 来试试:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
<script> document.addEventListener( 'DOMNodeInserted' , function (e) { var node = e.target; if (/xss/.test(node.src) || /xss/.test(node.innerHTML)) { node.parentNode.removeChild(node); console.log( '拦截可疑模块:' , node); } }, true ); </script> <button id= "btn" >创建脚本</button> <script> btn.onclick = function () { var el = document.createElement( 'script' ); el.src = 'http://www.etherdream.com/xss/out.js?dynamic' ; document.body.appendChild(el); }; </script> |
遗憾的是,DOMNodeInserted 也没能拦截动态脚本的执行 —— 尽管能检测到。经过一番尝试,所有浏览器都宣告失败。
当然,能实时预警已满足我们的需求了。但若能拦截动态脚本,整套系统防御力就更高了。
既然无法通过监控节点挂载来拦截,我们不妨换一条路。问题总有解决的方案,就看简单与否。
属性拦截
仔细分析动态脚本创建的所有步骤:
1
2
3
|
var el = document.createElement( 'script' ); el.src = 'http://www.etherdream.com/xss/out.js?dynamic' ; document.body.appendChild(el); |
是哪一步触发了挂载事件?显然是最后行。要获得比它更高的优先级,我们只能往前寻找。
既然是动态创建脚本,赋予它 src 属性必不可少。如果创建脚本只为赋值 innerHTML 的话,还不如直接 eval 代码更简单。
如果能在属性赋值时进行拦截,那么我们即可阻止赋予可疑的 src 属性。
类似 IE 有个 onpropertychange 事件,HTML5 里面也是有属性监听接口的,并且就是刚刚我们使用的那个:MutationEvent。甚至还是那两套方案:DOMAttrModified 和 MutationObserver。
在根节点上监听属性变化,肯定会大幅影响页面的性能,但我们还是先来看看是否可行。
先尝试 MutationObserver:
1
2
3
4
5
6
7
8
9
10
11
|
var observer = new MutationObserver( function (mutations) { console.log(mutations); }); observer.observe(document, { subtree: true , attributes: true }); var el = document.createElement( 'script' ); el.src = 'http://www.etherdream.com/xss/out.js?dynamic' ; document.body.appendChild(el); |
站外脚本执行了,但奇怪的是,回调却没有触发。原来,我们监控的是 document 下的元素,而脚本赋值时还处于离屏状态,显然无法将事件冒泡上来。
如果我们先 appendChild 再赋值 src 属性,倒是可以捕获到。但现实中调用顺序完全不是我们说了算的。
同样的,DOMAttrModified 也有这问题。
看来,事件这条路的局限性太大,我们得另辟蹊径。
API 拦截
监控属性赋值的方式肯定不会错,只是我们不能再用事件那套机制了。
想在修改属性时触发函数调用,除了事件外,另一个在传统语言里经常用到的、并且主流 JavaScript 也支持的,那就是 Setter 访问器。
当我们设置脚本元素 src 属性时,理论上说 HTMLScriptElement.prototype.src 这个访问器将被调用。如果我们重写这个访问器,即可在设置脚本路径时将其拦截。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
<script> HTMLScriptElement.prototype.__defineSetter__( 'src' , function (url) { console.log( '设置路径:' , url); }); </script> <button id= "btn" >创建脚本</button> <script> btn.onclick = function () { var el = document.createElement( 'script' ); el.src = 'http://www.etherdream.com/xss/out.js?dynamic' ; document.body.appendChild(el); }; </script> |
如果这套方案可行的话,一切都将迎刃而解。而且我们只监听脚本元素的 src 赋值,其他元素和属性则完全不受影响,因此性能得到极大提升。
经测试,FireFox 和 IE 浏览器完全可行。我们事先保存原始的 setter 变量,然后根据策略,决定是否向上调用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
<script> var raw_setter = HTMLScriptElement.prototype.__lookupSetter__( 'src' ); HTMLScriptElement.prototype.__defineSetter__( 'src' , function (url) { if (/xss/.test(url)) { if (confirm( '试图加载可疑模块:\n\n' + url + '\n\n是否拦截?' )) { return ; } } raw_setter.call( this , url); }); </script> <button id= "btn" >创建脚本</button> <script> btn.onclick = function () { var el = document.createElement( 'script' ); el.src = 'http://www.etherdream.com/xss/out.js?dynamic' ; document.body.appendChild(el); }; </script> |
效果非常漂亮,然而现实却令人遗憾 —— 我们的主流浏览器 Chrome 并不支持。由于无法操作原生访问器,即使在原型链上重写了 setter,实际赋值时仍不会调用我们的监控程序。
先不急,若是抛弃原型链,直接在元素实例上定义访问器又会如何?
1
2
3
4
5
6
7
8
9
10
11
12
13
|
<button id= "btn" >创建脚本</button> <script> btn.onclick = function () { var el = document.createElement( 'script' ); el.__defineSetter__( 'src' , function (url) { console.log( '设置路径:' , url); }); el.src = 'http://www.etherdream.com/xss/out.js?dynamic' ; document.body.appendChild(el); }; </script> |
这一回,Chrome 终于可以了。
然而,这仅仅是测试。现实中哪有这样的机会,供我们装上访问器呢。
因此,我们只能把主动防御的时机再往前推,在元素创建时就调用我们的防御代码。我们得重写 createElement 这些能创建元素 API,只有这样,才能第一时间里,给实例装上我们的钩子程序,为 Chrome 实现动态模块的防御:
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
|
<script> // for chrome var raw_fn = Document.prototype.createElement; Document.prototype.createElement = function () { // 调用原生函数 var element = raw_fn.apply( this , arguments); // 为脚本元素安装属性钩子 if (element.tagName == 'SCRIPT' ) { element.__defineSetter__( 'src' , function (url) { console.log( '设置路径:' , url); }); } // 返回元素实例 return element; }; </script> <button id= "btn" >创建脚本</button> <script> btn.onclick = function () { var el = document.createElement( 'script' ); el.src = 'http://www.etherdream.com/xss/out.js?dynamic' ; document.body.appendChild(el); }; </script> |
这样,当元素创建时,就已带有我们的属性扫描程序了,Chrome 不支持的问题也迎刃而解。
事实上,除了重写 property 访问器,我们还得考虑通过 setAttribute 赋值 src 的情况。因此需整理出一套完善的浏览器钩子程序。
XSS 前端防火墙(2):可疑模块拦截的更多相关文章
- XSS前端防火墙
前一段时间,在EtherDream大神的博客里看到关于XSS防火墙的一系列文章,觉得很有意思.刚好科创要做一个防火墙,就把XSS前端防火墙作为一个创新点,着手去实现了. 在实现过程中,由于各种原因,比 ...
- XSS 前端防火墙 —— 天衣无缝的防护
上一篇讲解了钩子程序的攻防实战,并实现了一套对框架页的监控方案,将防护作用到所有子页面. 到目前为止,我们防护的深度已经差不多,但广度还有所欠缺. 例如,我们的属性钩子只考虑了 setAttribut ...
- XSS 前端防火墙 —— 无懈可击的钩子
昨天尝试了一系列的可疑模块拦截试验,尽管最终的方案还存在着一些兼容性问题,但大体思路已经明确了: 静态模块:使用 MutationObserver 扫描. 动态模块:通过 API 钩子来拦截路径属性. ...
- XSS 前端防火墙(3):无懈可击的钩子
昨天尝试了一系列的可疑模块拦截试验,尽管最终的方案还存在着一些兼容性问题,但大体思路已经明确了: 静态模块:使用 MutationObserver 扫描. 动态模块:通过 API 钩子来拦截路径属性. ...
- XSS 前端防火墙(4):天衣无缝的防护
例如,我们的属性钩子只考虑了 setAttribute,却忽视还有类似的 setAttributeNode.尽管从来不用这方法,但并不意味人家不能使用. 例如,创建元素通常都是 createEleme ...
- XSS 前端防火墙 —— 可疑模块拦截
上一篇介绍的系统,已能预警现实中的大多数 XSS 攻击,但想绕过还是很容易的. 由于是在前端防护,策略配置都能在源代码里找到,因此很快就能试出破解方案.并且攻击者可以屏蔽日志接口,在自己电脑上永不发出 ...
- XSS 前端防火墙 —— 整装待发
到目前为止,我们把能用前端脚本防御 XSS 的方案都列举了一遍. 尽管看起来似乎很复杂累赘,不过那些是理论探讨而已,在实际中未必要都实现.我们的目标只是为了预警,能发现问题就行,并非要做到滴水不漏的程 ...
- XSS 前端防火墙(5): 整装待发
到目前为止,我们把能用前端脚本防御 XSS 的方案都列举了一遍. 尽管看起来似乎很复杂累赘,不过那些是理论探讨而已,在实际中未必要都实现.我们的目标只是为了预警,能发现问题就行,并非要做到滴水不漏的程 ...
- XSS 前端防火墙(1):内联事件拦截
关于 XSS 怎样形成.如何注入.能做什么.如何防范,前人已有无数的探讨,这里就不再累述了.本文介绍的则是另一种预防思路. 几乎每篇谈论 XSS 的文章,结尾多少都会提到如何防止,然而大多万变不离其宗 ...
随机推荐
- for语句中声明变量
在C语言中,局部变量应该在函数的可执行语句之前定义,但在C++中变量可在任何语句位置定义,只要允许程序语句的地方,都允许定义变量. 在C99标准中C同C++一样允许在for循环语句中定义变量.并且这个 ...
- Hadoop基础教程之高级编程
从前面的学习中,我们了解到了MapReduce整个过程需要经过以下几个步骤: 1.输入(input):将输入数据分成一个个split,并将split进一步拆成<key, value>. 2 ...
- SPRING IN ACTION 第4版笔记-第十一章Persisting data with object-relational mapping-005Spring-Data-JPA例子的代码
一.结构 二.Repository层 1. package spittr.db; import java.util.List; import org.springframework.data.jpa. ...
- Linux系统文件的隐藏属性
linux系统的文件除了有普通rwx权限外还有一种隐藏权限,例如明明有权限删除某个文件却报错了. 或者仅能为某个文件追加内容而不能减少内容,遇到这种很‘奇怪’的文件,就要怀疑是文件被设置隐藏权限了. ...
- Android 广播(内部类)
1.广播定义在一个单独的文件中 源码: public class MessageReceiver extends BroadcastReceiver{ @Override public void on ...
- 在Hadoop1.2.1分布式集群环境下安装hive0.12
在Hadoop1.2.1分布式集群环境下安装hive0.12 ● 前言: 1. 大家最好通读一遍过后,在理解的基础上再按照步骤搭建. 2. 之前写过两篇<<在VMware下安装Ubuntu ...
- Hadoop namenode无法启动问题解决
原因:在root账户(非hadoop账户)下操作hadoop会导致很大的问题 首先运行bin/start-all.sh发现namenode没有启动 只有它们 9428 DataNode 9712 Jo ...
- Fedora 15 KDE中如何打开software management及如何应用
Fedora 15 KDE中如何打开software management级如何应用 software management中有转载和卸载软件(Get and remove software)的功能 ...
- jenkins-slave的搭建和使用
一 什么是Jenkins的分布式构建和部署 Jenkins的分布式构建,在Jenkins的配置中叫做节点,分布式构建能够让同一套代码或项目在不同的环境(如:Windows和Linux系统)中编译.部署 ...
- 蓝牙接收苹果手机通知 ANCS协议分析
蓝牙接收苹果手机通知 ANCS协议分析 转载,请注明出处:http://www.cnblogs.com/alexcai/p/4321514.html 综述 现在有许多蓝牙手表.手环都能接收苹果ipho ...