前一段时间,在EtherDream大神的博客里看到关于XSS防火墙的一系列文章,觉得很有意思。刚好科创要做一个防火墙,就把XSS前端防火墙作为一个创新点,着手去实现了。

在实现过程中,由于各种原因,比如说JavaScript不熟练啦,SQL注入防火墙的干扰啦,出现了一系列问题,在文章中也会提到。

0x00 对XSS的分类

根据触发方式和可执行脚本的位置,把XSS分成如下几类,每一种都有不同的防御方式:

1) 内嵌型,直接内嵌在HTML标签中的一些可执行JS代码的属性中,如:

<a href="#" onclick="alert(document.cookie)">test</a>
<img src="1" onerror="alert(document.cookie)">
<a href="javascript:alert(document.cookie)">test</a>

以上是比较基础也比较有代表性的几个例子,当然还有一些变形:

<a href="data:text/html;base64,PHNjcmlwdD5hbGVydCgzKTwvc2NyaXB0Pg==">test</a>
<a onclick="alert((+[][+[]]+[])[++[[]][+[]]]+([![]]+[])[++[++[[]][+[]]][+[]]]+([!![]]+[])[++[++[++[[]][+[]]][+[]]][+[]]]+([!![]]+[])[++[[]][+[]]]+([!![]]+[])[+[]])" href="javascript:void">test</a>
<svg><a xlink:href="javascript:alert(14)"><rect width="1000" height="1000" fill="white"/></a></svg>

2) 静态外联型,成块/文件直接嵌套在页面中,如:

<script src="evil.js"></script>
<SCRIPT TYPE="text/javascript">alert('hacked by bb');</SCRIPT>

这一块儿变形比较少,因为再怎么变,也都要引入一个script标签。而针对script标签(不包括script脚本的内容)的防御,莫过于URL黑白名单、URL关键字。

3) 动态外联型,动态添加script标签(恶意代码在script标签中)

<script type="text/javascript">
var temp = document.createElement('script')
temp.src = 'xss.js'
document.body.appendChild(temp)
</script><br/>

最常见的类型如上,在能执行脚本的地方,就能添加新的script标签。

4) 其他,iframe是比较危险的标签,为了防止通过iframe绕过防火墙,我们也做了一些处理。

<iframe srcdoc="<script>alert('bb')<\/script>"></iframe><br/>
<iframe src="eval.htm"></iframe><br/>

0x10 内嵌型XSS的防御

针对内嵌型XSS的防御思想是: 在触发事件执行代码前,检测代码是否有害。

为了实现这个,需要用到addEventListener对全局的事件进行监听。为了获取所有的可触发的事件,我们遍历了document所有的事件(以on开头,如onclick、onmouseout):

 for (var k in document) {
if (/^on./.test(k)) {
//绑定事件
}
}

绑定事件,同时做到在触发事件之前先触发我们的检测脚本,需要用到addEventListener中的第三条属性,也就是:

document.addEventListener('click', function (e) {
//检测代码
}, true);

在这一块儿,出现的比较突出的问题是:

1. 性能问题。 鼠标的移动,会触发很多次检测,而大多数检测都是没有意义的。

2. 判断是否为恶意代码问题。 要有一个比较完善的判断机制。

3. JS事件冒泡机制。JS事件会一层一层向外冒泡,可以参考js冒泡机制

解决方案:

1. 添加了hash机制,对已经扫描过的事件,直接跳过。

2. 完善恶意代码检测函数,我们初步的检测代码如下:

function xss_test(code){
var keyword=['xss','eHNz','xss','xss','\\u0078\\u0073\\u0073',
'\\x78\\x73\\x73','\\170\\163\\163',/*特殊格式XSS*/
'alert\\\(\\s*\\d+\\\)','alert\\\(test\\\)','hacked',/*匹配alert(1),alert(test)*/
'String.fromCharCode','document.cookie',
'(\\\[\\\].*){3,}',/*匹配[]![]类的变形*/'data:text/html',/*匹配(URL编码|base64)的变形*/
'(&#x[0-9a-f]{2,}.*){3,}','(&#\\d{2,}.*){3,}',/*匹配HTML HEX变形*/
'(\\\x?[0-9a-f]{2,}.*){3,}',/*匹配JS HEX变形*/,'(\\\u\\d{2,}){3,}'/*匹配unicode变形*/
]; var pattern=new RegExp(get_reg(keyword),"i");
if(pattern.test(code)){
return true;
} return false;
}

3. 检测的时候,逐层递归。

0x20 静态外联型XSS的防御

基本防御思想:动态监听元素的添加,并拦截

用到了HTML5中的MutationObserver,监听元素的变动(添加),在添加的时候,检测添加的内容(node.innerHTML)或URL(node.src)中是否含有恶意关键字、URL是否在白名单/黑名单中。如果检测到攻击,就删除当前元素。

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 (node.tagName == 'SCRIPT') {
//一些检测代码
}
}
}
}

遇到的比较突出的问题是,删除的时候,由于多种原因(浏览器对HTML5的支持性、各浏览器的兼容性、删除时结点可能尚未被挂载到页面中等)可能会造成删除失败,恶意代码仍会执行。在实际应用中,没有找到合适的解决办法。但无论是否能够删除,添加的结点总是能够被捕获到,同样能起到很好的监听作用。

在处理的过程中,还遇到了黑名单和白名单。黑白名单都是从后台添加的,基本功能很简单:当遇到白名单,直接跳过,遇到黑名单,就直接删除。只是白名单的时候要提一点,既然是漏洞预警防火墙,所以必须有记录数据/拦截日志的地方(管理中心),而在发送数据的时候,不免要用到跨域,而且跨域的时候发送的数据还有可能携带XSS特征,很有可能被拦截掉,所以白名单中,一定要添加我们管理中心的URL。在我们的防火墙系统中,默认管理中心为:http://127.0.0.1:1337/index.html。

匹配黑白名单的时候,用到了简单的正则:

1. 从当前URL中获取host:port

src = script_src.match(/(http:\/\/|https:\/\/)?([^\/]*)/)[2]

2. 组装黑/白名单的正则:

var OutsiteWhiteList = ["127.0.0.1:8080","baidu.com"]
/** 正则表达式生成函数
** @input : keyword 数组形式,如['xss','x ss']
** @output: 格式化的正则表达式,如(xss|x ss)
**/
function get_reg(keyword){
var str='(';
for(var i in keyword){
str+=keyword[i]+"|";
} return str.length>1?str.slice(0,-1)+')':false;
}

0x30 动态外联型XSS的防御

基本防御思想:JavaScript Hijacking,也就是JS钩子,勾住一些关键函数,并添加检测代码

关于JavaScript Hijacking,有一篇很经典的文章可以参考:浅谈javascript函数劫持。说到关键函数,现在实现的钩子包含了常用的函数,包括createElement、setAttribute两个函数,实现了对最常用的动态创建元素的监控。

var raw_createElement = Document.prototype.createElement;
Document.prototype.createElement = function () {
var element = raw_createElement.apply(this, arguments); // 为脚本元素安装属性钩子
if (element.tagName == 'SCRIPT') {
element.__defineSetter__('src', function (url) {
element.setAttribute("src",url)
});
} return element; //createElement函数需要返回一个对象
}; var raw_setAttribute = Element.prototype.setAttribute;
Element.prototype.setAttribute = function (name, url) {
if (this.tagName == 'SCRIPT' && /^src$/i.test(name)) {
var res = url_test(url);
switch (res) {
case 'white_list':
break;
case 'url_word':
xss_notice(, 'null', url);
return;
case 'black_list':
xss_notice(, 'null', url);
return;
default:
break;
}
}
raw_setAttribute.apply(this, arguments); //setAttribute并不需要返回值
};

能够实现如下格式的动态创建元素的检测:

//1. src赋值
var temp = document.createElement('script')
temp.src = 'xss.js'
document.body.appendChild(temp) //2. setAttribute赋值
var temp = document.createElement('script')
temp.setAttribute('src','xss.js')
document.body.appendChild(temp)

在开发过程中,遇到最突出的问题是钩子逻辑错误,导致无限循环(= =页面崩溃了N次)。出现的原因是这样:在给createElement - src类型做检测的时候,很脑残的把代码写成了这样:

var raw_createElement = Document.prototype.createElement;
Document.prototype.createElement = function () {
var element = raw_createElement.apply(this, arguments); // 为脚本元素安装属性钩子
if (element.tagName == 'SCRIPT') {
element.__defineSetter__('src', function (url) {
//正确写法:element.setAttribute("src",url)
element.src=url //错误写法,无限循环调用自身
});
} return element; //createElement函数需要返回一个对象
};

除了用setAttribute来赋值外,还可以用钩子的方法来实现,勾住原始函数需要调用lookupsetter函数:

var raw_setter = HTMLScriptElement.prototype.__lookupSetter__('src');

同时为了防止我们的函数不会再次被钩子勾住,然后进行修改,我们还要对钩子进行一些处理,让它不可写。

//锁死call和apply,防止盗用和重写
Object.defineProperty(Function.prototype, 'call', {
value: Function.prototype.call,
writable: false,
configurable: false,
enumerable: true
});

一定要提到的一点,是能够动态创建元素的函数不止这些,以上的函数,只是完成了最基本的钩子,有经验的攻击者很简单的可以绕过。

0x40 关于Iframe

以上说到的防火墙代码,都是针对当前的页面进行防御。如果通过iframe创建一个新页面,那不就绕过了?所以针对iframe做了如下的防御:

1. 不允许动态创建iframe。 直接在createElement的钩子里drop掉所有创建Iframe的语句。

2. 默认拦截所有的静态iframe,仅允许白名单中的iframe创建。 在MutationObserver中设置。

3. 在所有允许创建的iframe里嵌入防火墙代码,层层递归,保护安全。

4. 针对特别格式的,如srcdoc,直接禁掉

<iframe srcdoc="<script src=white.js><\/script>"></iframe>

<script>
//对于iframe,由于不常用,所以,监控所有的Iframe元素,直接用白名单过滤
if (node.tagName == 'IFRAME') {
if (node.getAttribute('srcdoc')) { //动态添加的srcdoc 同样可以被拦截
delete_node(node, 401);
}
else if (node.src && !reg_test(IframeWhiteList, node.src)) { //如果没有被白名单匹配到
delete_node(node, 402);
}
}
</script>

0x50 可执行函数重写

有很多函数,比如eval、setInterval等,相当于PHP中的eval,同样可以造成很大的危害。~。~所以继续上钩子

var raw_setInterval =setInterval;
setInterval = function(func, delay){
if(xss_test(func)){
xss_notice(,'',func);
}
else{
raw_setInterval(func,delay)
}
};
setInterval.constructor=undefined;

需要注意的一点,是要把constructor置为空,否则可以调用setInterval.constructor绕过。

0x60 总结

以上的描述基本能够应对常见的XSS攻击,并能起到一定程序的预警作用。我们已经基本完成了前端防火墙的开发,并且为其编写了一个基于express的后台,搭配使用,可以做到漏洞回放、日志查看、防火墙设置等基本功能。

以上大致的总结了各模块工作的基本模式,以及在开发过程中遇到的问题。一些细节写的不是太详细,如果你感兴趣,可以在http://git.oschina.net/friday_bb/waf看到我们的代码,欢迎来交流。

XSS前端防火墙的更多相关文章

  1. XSS 前端防火墙 —— 整装待发

    到目前为止,我们把能用前端脚本防御 XSS 的方案都列举了一遍. 尽管看起来似乎很复杂累赘,不过那些是理论探讨而已,在实际中未必要都实现.我们的目标只是为了预警,能发现问题就行,并非要做到滴水不漏的程 ...

  2. XSS 前端防火墙(5): 整装待发

    到目前为止,我们把能用前端脚本防御 XSS 的方案都列举了一遍. 尽管看起来似乎很复杂累赘,不过那些是理论探讨而已,在实际中未必要都实现.我们的目标只是为了预警,能发现问题就行,并非要做到滴水不漏的程 ...

  3. XSS 前端防火墙(1):内联事件拦截

    关于 XSS 怎样形成.如何注入.能做什么.如何防范,前人已有无数的探讨,这里就不再累述了.本文介绍的则是另一种预防思路. 几乎每篇谈论 XSS 的文章,结尾多少都会提到如何防止,然而大多万变不离其宗 ...

  4. XSS 前端防火墙 —— 可疑模块拦截

    上一篇介绍的系统,已能预警现实中的大多数 XSS 攻击,但想绕过还是很容易的. 由于是在前端防护,策略配置都能在源代码里找到,因此很快就能试出破解方案.并且攻击者可以屏蔽日志接口,在自己电脑上永不发出 ...

  5. XSS 前端防火墙(2):可疑模块拦截

    由于是在前端防护,策略配置都能在源代码里找到,因此很快就能试出破解方案.并且攻击者可以屏蔽日志接口,在自己电脑上永不发出报警信息,保证测试时不会被发现. 昨天提到最简单并且最常见的 XSS 代码,就是 ...

  6. XSS 前端防火墙 —— 天衣无缝的防护

    上一篇讲解了钩子程序的攻防实战,并实现了一套对框架页的监控方案,将防护作用到所有子页面. 到目前为止,我们防护的深度已经差不多,但广度还有所欠缺. 例如,我们的属性钩子只考虑了 setAttribut ...

  7. XSS 前端防火墙 —— 无懈可击的钩子

    昨天尝试了一系列的可疑模块拦截试验,尽管最终的方案还存在着一些兼容性问题,但大体思路已经明确了: 静态模块:使用 MutationObserver 扫描. 动态模块:通过 API 钩子来拦截路径属性. ...

  8. XSS 前端防火墙(3):无懈可击的钩子

    昨天尝试了一系列的可疑模块拦截试验,尽管最终的方案还存在着一些兼容性问题,但大体思路已经明确了: 静态模块:使用 MutationObserver 扫描. 动态模块:通过 API 钩子来拦截路径属性. ...

  9. XSS 前端防火墙(4):天衣无缝的防护

    例如,我们的属性钩子只考虑了 setAttribute,却忽视还有类似的 setAttributeNode.尽管从来不用这方法,但并不意味人家不能使用. 例如,创建元素通常都是 createEleme ...

随机推荐

  1. [改善Java代码]列表相等只需关系元素数据

    来看一个判断列表相等的例子,看代码: import java.util.ArrayList; import java.util.Vector; public class Client { public ...

  2. [未完成]关于DOM的总结

    这样有什么好处吗? 一但这些东西变成了节点对象,意味着每一个节点对象都会有很多属性和行为提供出来. 如果div是一个对象,那么就可以针对这个对象调用其中的一些方法,对div操作. 这个操作可以包括,比 ...

  3. 关于windows中的快捷键

    Windows快捷键大全编辑 目录1快捷方式 2IE浏览器 3小键盘 4WIN键 5资源管理器 6对话框7我的电脑 8放大程序 9辅助选项 10XP键盘 11对话框 12自然键盘13辅助键盘 14键盘 ...

  4. Android自动化测试介绍

    1.随机事件测试.通过adb命令执行测试Monkey 就是SDK中附带的一个工具, 用来做压力测试.应用程序crash 和 ANR时会产生日志. 然后根据关键字分析,就可以把应用出现的问题抓出来. 2 ...

  5. server 2003上为单点登录sso配置映射

    单点登录不是本人做的,代码需要调用类似 http://***.com/login.sso 的地址.要成功调用,需要在IIS设置.sso为后缀的映射项. Win7系统下一设置完,就能成功调用. 但是服务 ...

  6. JavaScript “\”替换成 “\\”

    str=str.replace(/\\/g,'\\\\');

  7. jqueryeasyui中文乱码问题

    下载的Demo中charset=utf-8,手动改成gb3212,问题解决.

  8. batch 数字进制的问题

    when set viable to number type in cmdexample: set /a num=0833echo %num% display: Invalid number.  Nu ...

  9. 返回页面,主页面不刷新window.history.go(-1),主页面刷新window.location.go(-1)

    返回上一页,不刷新 window.history.go(-1) 返回上一页,刷新 window.location.go(-1)

  10. iOS开发 中的代理实现

    iOS开发 中的代理实现 关于今天为什么要发这篇文字的原因:今天在和同事聊天的时候他跟我说项目中给他的block有时候不太能看的懂,让我尽量用代理写,好吧心累了,那就先从写个代理demo,防止以后他看 ...