• XSS前端防火墙


    前一段时间,在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','&#120;&#115;&#115;','&#x78;&#x73;&#x73;','\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(301, 'null', url);
                    return;
                case 'black_list':
                    xss_notice(302, '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(503,'',func);
        }
        else{
            raw_setInterval(func,delay)
        }
    };
    setInterval.constructor=undefined;    

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

    0x60 总结

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

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

  • 相关阅读:
    ScrollView反弹效果的实现
    Unity 3D本地公布WebPlayer版时&quot;Failed to download data file&quot;解决方式
    win7休眠的开启与关闭方法命令行操作和图文结合的鼠标操作
    使用Javascript D3创建属于你的涂鸦作品
    android获取自己定义控件位置坐标,屏幕尺寸,标题栏,状态栏高度
    [Python]Use Flask-Admin with PostgreSQL
    [LeetCode] Best Time to Buy and Sell Stock
    spring实战五之Bean的自动检测
    FireBug使用总结
    javascript的window.onload()方法和jQuery的$(document).ready()的对比
  • 原文地址:https://www.cnblogs.com/kuoaidebb/p/4558126.html
Copyright © 2020-2023  润新知